I made a commit that includes the following changes:
main
to a new file aux
aux
Unfortunately, this makes the commit hard to review on GitHub, because GitHub displays the diff as if the whole chunk was deleted and reinserted. Ideally the reviewer would only look at the very minor edits made to aux
after the content move.
I would like to add an intermediate commit to simplify reviewing this change. The intermediate commit would simply duplicate main
to aux
(i.e., aux
would be a copy of main
). Then the next commit would remove the moved content from main
and exhibit the minor changes to aux
. Is this a good way of doing it, and if so, how should I go about creating this intermediate commit? Note that unfortunately I have already completed the change, and I would prefer not to redo the entire process.
This can be done, but be aware that if you've published the commit (as it sounds like you have), then any history editing is not recommended. So there are a few possible procedures. For purposes of these explanations I'll assume the changes are on a branch my_branch
. (It works fine if the branch in question is master
; I just needed to assume something to write commands with.)
Simple But Clunky
You could go back to the previous commit, create a temporary branch, and create your intermediate commit on that branch. Then tell reviewers to first review the change on the temporary branch, then diff your my_branch
against the temporary branch. If your reviewers will go for that, then it gives them the simpler diffs with no messiness. But maybe it doesn't preserve the history in the nicest way.
With History Rewriting
If you can do it, this will get you a cleaner history. But it involves history rewriting, and as noted above this could cause problems for other users of any repo(s) to which the change has been pushed. See the git rebase
documentation re: Recovering from an upstream rebase
If this isn't a problem, or if you find that the recovery procedure is acceptable, then you could do this:
First, checkout the commit prior to the change. I'm assuming that's
git checkout my_branch^
Now you're in detached head state. Probably good to create a temporary branch:
git checkout -b temp_branch
Copy main
to aux
and commit.
Next you want a commit that looks just like the my_branch
HEAD, but you want its parent to be the temp_branch
HEAD. If aux
is the only thing that changed in that commit:
git checkout my_branch -- aux
git commit
git branch -f my_branch
If there are other changes in the commit, then it may be easier to re-parent it. There's a stock example of how to do this in the documentation for git filter-branch
(https://git-scm.com/docs/git-filter-branch)
Of course at this point you can clean up temp_branch
. You may have to "force push" my_branch
(if it had already been published).
git checkout my_branch
git push -f
And that's the signal that all the other developers may need to recover if they also have references to my_branch
.
Without History Rewriting
If neither of the above can work, then you could try this: First, use git revert
to go back to before the change. Then, re-apply the commit in two phases.
Again the intermediate commit is easy enough to make (same as in the rewrite case). If the changes to main
and aux
are the only change in the commit, then you can again pull aux
from the original commit to simplify creating the final commit. But this time the command will be
git checkout HEAD^ -- aux
because HEAD
is the revert commit so HEAD^
is the one with the change in it.
If the commit has additional changes, then a braoder
git checkout HEAD^ -- .
might do, but I'd be inclined to rm -r
the work tree first just out of paranoia. Whatever steps you attempt, you can validate that you've recreated the final tree correctly with
git diff HEAD^
So now you have this weird back-and-forth int he history, but there's no rewrite and no "second branch" involved in doing a simplified validation. Of course you might still have to validate in two steps: First show that HEAD^
is equal to the previous validated state, then validate the patch from HEAD^
to HEAD
.