1

With interactive rebase (git -i rebase ...) one can edit commits anywhere in the current branch's lineage, thus "rewriting history".

A given commit, however, can belong to the lineages of multiple branches.

For example, suppose I have a repo with this branch structure:

A --- B --- C --- D --- F --- G branch_1* \ `-- H --- I --- J branch_2 \ `-- K branch_3 

If the active branch is branch_1, and I use rebase -i to edit commit B, the resulting repo would look like this (at least until GC happens):

 ,-- b --- c --- d --- f --- g branch_1* / A --- B --- C --- D --- F --- G \ `-- H --- I --- J branch_2 \ `-- K branch_3 

Note that the original B continues to be in the lineages of branch_2 and branch_1. Repeating the process for each of these branches, as tedious as that would be, would result in multiple redundant commits, and the original branch structure would be lost:

 ,-- b --- c --- d --- f --- g branch_1 / A --- B --- C --- D --- F --- G | \ | `-- H --- I --- J | \ | `-- K |\ | `-- b' -- c' -- h --- i --- j branch_2* \ `-- b'' - c'' - h' -- i' -- k branch_3* 

Note that b, b', and b'' are essentially equivalent commits. The same thing goes for c, c', and c'', h and h', and i and i'.

Is there a halfway convenient way to achieve something like this:

A --- b --- c --- d --- f --- g branch_1* \ `-- h --- i --- j branch_2 \ `-- k branch_3 

...where the modification to the B commit gets propagated through all the lineages it belongs to?

(I'd prefer a solution that also propagates the changes through to all the descendant stashes.)

1
  • You can't, but the middle case where you lose the structure is nonsense. Rebase off the new b and you preserve the structure. Commented Feb 22, 2020 at 0:36

2 Answers 2

3

... is there a halfway convenient way to achieve [a sensible result]

No.

There's one command, git filter-branch, that can do it, but it's not halfway convenient, nor even 1/4th convenient. It's about 1000% inconvenient. :-)

The new git rebase --rebase-merges machinery is pretty obviously adaptable to doing this sort of thing in a more convenient way,1 but it's not currently designed to rebase multiple branch names.

The new experimental git filter-repo is capable enough to do what you want, and probably less inconvenient than git filter-branch, but it's still swatting bugs with nuclear weapons—in this case, maybe a moderately large bug, not just a fly, but still serious overkill.

(I once wrote my own experimental thing to do this sort of rebase, but I never finished it. It did what I needed it to, when I needed it. It worked using the equivalent of repeated git rebase --onto operations and had a lot of corner cases.)


1The key is to be able to label particular commits, so that after rebase copies them, you can pair up the <old, new> hash-ID pairs, and otherwise jump around the graph structure from one chain to another as the rebase progresses. The old --preserve-merges code could not do that; the new --rebase-merges code can. Once you have these pieces in place, rebasing multiple branches "simultaneously" is just a matter of saving multiple branch names to force-adjust after the rebase completes. You then have the rebase list the correct commits and jump-points. The main bulk of the rebase operation consists of copying those commits. Last, using the old-to-new mapping, rebase can adjust each branch name, then reconnect HEAD to the one you want.

The remaining user-interface level problem lies in selecting the correct set of branch names to multi-rebase.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks. I find it surprising that this commonsensical capability has not been implemented. Maybe it's too difficult to get right. BTW, a complete solution should also transfer tags, something I didn't dare to hope for in my original post.
Both filter-branch and filter-repo can adjust tags (though you lose any PGP signatures). Current rebase will copy git notes entries; filter-branch doesn't and I don't know about filter-repo.
1

As you say, if you rebase branch_1, changing B, you will get:

 ,-- b --- c --- d --- f --- g branch_1* / A --- B --- C --- D --- F --- G \ `-- H --- I --- J branch_2 \ `-- K branch_3 

You can then rebase branch_2 onto c with:

git rebase --onto c C 

yielding:

 ,-- b --- c --- d --- f --- g branch_1* / \ | `-- h --- i --- j branch_2 | A --- B --- C --- D --- F --- G \ `-- H --- I --- J \ `-- K branch_3 

and then rebase branch_3 onto i:

git rebase --onto i I 

yielding:

 ,-- b --- c --- d --- f --- g branch_1* / \ | `-- h --- i --- j branch_2 | \ | `-- j branch_3 | A --- B --- C --- D --- F --- G \ `-- H --- I --- J \ `-- K 

which will at least mirror your original branch structure.

2 Comments

This solution works for the toy example I gave, but it is hell to apply in the general case. It requires examining a potentially complex graph, and one-by-one identifying the appropriate rebase points. I've tried doing this sort of thing. It's insane.
I can see how it could become impractical for complex cases but it's the best solution I know of to the problem as posed.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.