kjo
kjo

Reputation: 35301

How to rewrite history for multiple branches simultaneously?

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.)

Upvotes: 1

Views: 502

Answers (2)

torek
torek

Reputation: 488013

... 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.

Upvotes: 3

Calum Halpin
Calum Halpin

Reputation: 2095

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.

Upvotes: 1

Related Questions