However you achieve the result, when you are done, you will not use the original three commits. If you're allowed to leave A
100% alone, you can keep the original A
, but if you must touch B
, you won't have the original B
any more, and must therefore make a new copy of C
as well.
So far, that's just a statement of fact, and not advice on how to achieve what you want. The way to get what you want is—usually—to use git rebase -i
.
Let's say you're on branch feature
right now:
...--o--o--o--o <-- main
\
A--B--C <-- feature (HEAD)
You simply run git rebase -i main
and Git comes up with an instruction sheet that tells Git to keep the three commits as-as:
# instructions
pick <hash-of-A> <subject-of-A>
pick <hash-of-B> <subject-of-B>
pick <hash-of-C> <subject-of-C>
Change the second pick
to edit
and write the instruction sheet back and exit your editor.1 Git will now start by trying to copy commit A
directly in place, which will succeed. It will then continue by trying to copy commit B
in place, which will also succeed, but now it will stop in the middle of this copying:
...--o--o--o--o <-- main
\
A--B <-- HEAD
\
C <-- feature
You will be in detached HEAD mode, with HEAD
selecting commit B
.
You can now make changes to the file(s) you would like to change, git add
, and run git commit --amend
. The --amend
will have Git make the new commit—let's call it B'
—using commit A
as its parent, instead of commit B
. The result looks like this:
...--o--o--o--o <-- main
\
A--B' <-- HEAD
\
B--C <-- feature
You can now run git rebase --continue
to make Git go on to the pick C
command. This will cherry-pick commit C
, making a new commit that we'll call C'
. Some merge conflicts can occur here, because cherry-pick is actually a merge. If so, you'll need to fix them up and resume again before commit C'
can be made. If no conflicts occur, though, we are now in this state:
...--o--o--o--o <-- main
\
A--B'-C' <-- HEAD
\
B--C <-- feature
This completes the set of operations that the interactive rebase is to perform, so it now does the last trick of any rebase, which is to yank the branch name to "here" (wherever HEAD
is now) and re-attach your HEAD
:
...--o--o--o--o <-- main
\
A--B'-C' <-- feature (HEAD)
\
B--C [abandoned]
Should you want to see the original commits, they still exist: you just have to find the hash ID of original commit C
. This is available in:
ORIG_HEAD
, for just a brief time (because ORIG_HEAD
keeps getting overwritten);
- the reflog for
HEAD
, for at least 30 more days by default; and
- the reflog for branch
feature
, for at least 30 more days by default.
The reflog entries have numeric-suffixed names: feature@{1}
. You can also use time-relative names, such as HEAD@{yesterday}
. Generally, if some name has had more than one change in any given time period, you'll want to run git reflog
, though, instead of trying to guess something like "yesterday.10.am".
1If you have a long-running editor (some variants of emacs, atom, etc), use whatever method it has to signal back to the waiting Git that this one file is done now and Git should resume.