You cannot do this at all. No part of any commit can ever be modified.
You can do something different, that may—or may not—be good enough.
Given any Git commit, you can:
- extract that commit, so that you can work on it
- do some work
- make a new commit, which will have a different hash ID
and having made that new commit, you can then repeat this. So, suppose you have this series of commits:
A--B <-- branch_1, origin/branch_1
\
C--D <-- branch_2, origin/branch_2
\
E--F <-- branch_3, origin/branch_3
\
G--H <-- branch_4, origin/branch_4
(this is the same graph you drew, except that I've used uppercase letters to stand in for the hash IDs, and put newer commits towards the right, rather than towards the top). It is completely impossible to change any of the existing commits, but we can extract D
to a work area, then make changes, then make a new and improved D'
:
A--B <-- branch_1, origin/branch_1
\
C--D <-- branch_2, origin/branch_2
\ \
D' \ <-- [remember this hash]
\
E--F <-- branch_3, origin/branch_3
\
G--H <-- branch_4, origin/branch_4
Now that we have D'
we copy E
atop it, changing one thing—its parent—so as to make E'
, the same way we copied but changed D
to make D'
:
A--B <-- branch_1, origin/branch_1
\
C--D <-- branch_2, origin/branch_2
\ \
D' \ <-- [remember this hash]
\ \
E' E--F <-- branch_3, origin/branch_3
\
G--H <-- branch_4, origin/branch_4
We'll need to copy F
to F'
and G
to G'
and H
to H'
as well, for a total of five commits copied to new-and-improved commits:
A--B <-- branch_1, origin/branch_1
\
C--D <-- branch_2, origin/branch_2
\ \
\ \ <-- [remember this hash]
\ \
\ E--F <-- branch_3, origin/branch_3
\ \
\ G--H <-- branch_4, origin/branch_4
\
D' <-- new_branch_2
\
E'-F' <-- new_branch_3
\
G'--H' <-- new_branch_4
We now have new branches, which are "just as good" as the old ones (because they have commits that are as good or better, but with different hash IDs). Now we must discard the old branch names in favor of the new ones, perhaps by renaming the old branches to old_*
and the new ones to take out the new_
.
(Our old_*
names are no longer very useful so we can delete them entirely, sooner or later.)
Finally, our remote-tracking names origin/*
are remembering branch names as stored in some other Git, so now we must convince that other Git repository to jettison its copies of those commits and start using our new copies. To do that, we will need to use git push --force
or something similar to send our new commits to that other Git and tell it abandon all the work you used to have, in favor of these new and improved commits. For instance:
git push --force-with-lease branch_2 branch_3 branch_4
would do the trick (and the --force-with-lease
would make sure that their hash IDs, which are in our origin/*
names, match up with what we think they do).
To achieve this in realistic situations, use git rebase
or git filter-branch
or similar
When the number of commits to "improve" like this is small, you can do it one commit at a time, using git cherry-pick
, leaving yourself markers: here is where new_branch_3 goes for instance. When it's large, this is painful. You will want to automate the process.
Unfortunately, this is tricky. Fortunately, there are several power tools for this, including git replace
. This lets you insert a graft. However, grafts do not transfer on clones: whoever clones your repository sees the original, un-grafted history. Fortunately, git filter-branch
or the new git filter-repo
can then transform the grafted clone into a clone with a new series of commits, updating branch names en-masse. See other StackOverflow answers for details.