I am currently submitting open-source code and the structure of commits are quite strict. I have three commits, let's call them A, B and C. It is important to note that all changes are within the same file.
Furthermore, I have received feedback on code within all three commits. Is it possible to drop into the staged code of, for example, commit B, edit a few lines of code, recommit and then reapply C without much hassle?
The best way I can think of right now is to undo all three commits and then go through the chunk staging again, which will take time to get perfect, since some code are in proximity.
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);HEAD
, for at least 30 more days by default; andfeature
, 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.