Search code examples
gitgit-filter-branch

How to reformat multiple branches at once in Git?


Let's suppose I've got a branch structure in Git that looks like master -> A -> B. That is, A contains commits on top of master and B contains commits on top of A.

I would like to reformat A, without resulting in a massive set of conflicts in B that will need to be resolved by hand. If I'm not mistaken, I could do this with git filter-branch, except I don't want anything in master to change. (I.e. I don't want to invalidate all the public Git commit ID in the project.)

Is there a way to git filter-branch on just A and B (or just as good for my purpose, all of the branches in the repo except master)?

Edit: In particular, is it sufficient to do git filter-branch ... A B? Will this avoid touching master and keep A and B consistent with each other (i.e. rewrite the common commits between A and B to the same commit IDs and not allow them to be duplicated, or something like that).


Solution

  • You have the right general idea here (of how to use filter-branch), but the wrong syntax. To get git filter-branch to copy (and filter) commits that are reachable from both names A and B but not to copy+filter commits that are reachable from master, use the syntax:

    git filter-branch <options> -- ^master A B
    

    What we're doing here, with the git rev-list arguments ^master A B, is telling Git: Find all commits reachable from either A or B, excluding all commits reachable from master. So if the commit chains go:

    ...--o--o--o   <-- master
                \
                 o--o--o   <-- A
                        \
                         o--o   <-- B
    

    for instance, this selects the middle and bottom row commits. It would even with ^master B or master..B, as in phd's answer, but this is better for two reasons. First, it would still select all middle-row commits with:

    ...--o--o--o   <-- master
                \
                 o--o--o   <-- A
                     \
                      o--o   <-- B
    

    (although this is presumably not your structure). Second, after copying (and filtering) the specified commits such that you wind up with:

                         o--o   <-- [filtered copy of commit to which B points]
                        /
                 o--o--o   [filtered copy of commit to which A points]
                /
    ...--o--o--o   <-- master
                \
                 o--o--o   <-- A
                        \
                         o--o   <-- B
    

    the filter-branch command will move all of what the documentation calls positive references. A positive reference is one that does not have a negation in front of it: in this case, A and B, but not master, as master is written ^master and is therefore a negative reference.

    Hence this won't touch master (not that it would anyway—its commit did not get copied) but will yank the names A and B over so that they point to the copied commits.

    Note that if you don't want changes made to the snapshots in the two (in this example) B-only commits, it is up to you to write a clever filter that does that. (Or to use a filter that makes no tree changes, but by "reformat" I assume you want to use --tree-filter and something like clang-format on the snapshots in each of the various A commits. It's probably more sensible to go ahead and make the same formatting changes to the various B commits as well.)