Reputation: 15009
Given this situation
A--B
| B1
| B2
| ...
| Bn label:b-branch
|
C--D1
| D2
| ...
| Dn label:d-branch
|
E
I would like to hook Bn
to D1
so we have a nice clean path, even though Bn
and D1
are totally unrelated. I don't need to do any fancy merging or otherwise affect any actual source code as the D
branch completely replaces the B
branch, I just want to somehow say that Bn
is now another parent of D1
along with C
, that is:
A--B
| B1
| B2
| ...
| Bn label:b-branch
| | <- Only this link is new
C--D1
| D2
| ...
| Dn label:d-branch
|
E
The code is hosted on a private GitLab instance, so I am aware everyone will have to sync up when we change history.
Upvotes: 0
Views: 30
Reputation: 488183
Start with git replace
. I'm not quite sure which way to read your graph, so I'll just say that you can use this with --edit
or with --graft
. That will make a replacement for some commit, with the replacement having whatever changes you like.
The new replacement is not actually in the graph! For instance, suppose I the following graph, which we read by starting at the right side and working leftwards to go back in time:
A <-B <-C <-D <-E <-- master
Commit E
has D
as its parent, which has C
as its parent, and so on.
We can make a new commit C'
whose parent is A
using git replace --graft hash-of-C hash-of-A
, giving:
A--B--C--D--E <-- master
\
C' <-- refs/replace/hash-of-C
When git log
or other similar commands walk the graph, they might start at E
and show it, then move to D
and show it. The tricky bit happens next: they move to C
, but at this point, they notice that refs/replace/hash-of-C
exists. They let go of C
entirely and move to C'
instead, and show that. They they move to C'
s parent A
and show that. The result is that B
seems to be gone. The same trick will work for your case: if you want to introduce two parents where there was just one, you make a replacement that has those two parents instead of just the one.
The drawback to this kind of replacement commit is that git clone
normally omits the refs/replace/
namespace and its commits. So someone cloning the repository sees the original commits—their Git has no refs/replace/hash
at all, and never copied the replacement commits.
You can arrange to get those cloned, but instead of doing that, you can now use an otherwise-no-op git filter-branch
to "cement" the replacements in place in a new repository. For instance, after:
git filter-branch --tag-name-filter cat -- --all
on the five-commits-and-one-replacement repository I drew above, you would have:
A--B--C--D--E <-- refs/original/refs/heads/master
\
C' <-- refs/replace/hash-of-C
\
D'-E' <-- master
Discarding the refs/original/
and refs/replace/
namespace names by making another clone, you wind up with:
A--C'-D'-E' <-- master
which has made the grafting permanent. So you can now either clone the repository and use the clone as the replacement repository, or just discard the refs/replace/
and refs/original/
names from the original repository to make it look the same as the proposed cloned replacement (except that the original objects tend to linger for some time—basically, until the garbage collector gets around to cleaning them up).
Upvotes: 2