You cannot modify any existing commit. This is a fundamental Git guarantee: any content, once saved, is saved that way forever, by the hash ID generated when saving it.
You can simply stop using any existing commit(s), and of course you can always make new commits. Hence you'll need to make a new commit that's almost the same as commit A
, but not quite exactly the same:
- extract existing commit
A
;
- remove the unwanted files;
- and last, make a new root commit
A'
that's a copy of A
in most ways, except for the fact that those files are now gone.
You now have:
A--B--C--D--E--F <-- old-master
A' <-- new-master
Of course, that's not quite what you need, because you still want the other commits. So now you must copy B
to B'
as well. The—or at least a—difference between B
and the new B'
is that B'
's parent is A'
, not A
. The existing B
has the existing A
as its parent, and you cannot change any existing commit.
So now you extract B
(and perhaps remove those same files again, if that's part of your goal), then make new commit B'
whose parent is A'
:
A--B--C--D--E--F <-- old-master
A'-B' <-- new-master
Obviously, you're still not done yet. You must now copy C
to C'
in the same way you copied B
to B'
, then repeat for the rest of the commits. Eventually you wind up with this:
A--B--C--D--E--F <-- old-master
A'-B'-C'-D'-E'-F' <-- new-master
and now you can use the new chain of commits as master
and you are done (well, except for creating the new branch(es), which must be done separately—you drew them as having B
attached to the last commit in the new branch, but I suspect you didn't quite mean it that way).
There is a Git command that does exactly this: git filter-branch
. The git filter-branch
command takes the branch(es) you name on the command line, finds every commit reachable from those names (hence F
then E
then D
then ... then A
), puts them into the right order (A
first, etc), and begins copying:
- extract original commit;
- apply each filter, in the order listed in the documentation;
- make a new commit whose parent(s) is/are the parent(s) made earlier during the filtering process (or for an original root, make a new root commit).
Once all the specified commits have been copied, git filter-branch
adjusts the branch names to point to the final copied commits.
Typically, to do an entire repository, you use --all
to copy all commits and adjust all branch names, along with --tag-name-filter cat
to adjust all tag names. You can use the --tree-filter
as it's the simplest: it lets you use ordinary file system operations on ordinary files, to manipulate the contents of each commit. But it's also the slowest filter, by far; so you may want to experiment until you can do the file-removal with the fastest filter, which is the --index-filter
.
(You'll still need to build the new branch separately, as git filter-branch
only changes any existing branch names, and builds a map of old-to-new commit hashes as it goes. It—unlike the BFG, which in some ways is better-designed—removes this map when it is done, but for your particular purposes you won't need it, you can just find the new root commit. It's wisest to experiment on a clone of the repository, probably a git clone --mirror
clone.)