This question begins where this one left off: Just like there, the situation is that, in one commit, a file A
was renamed to B
and new content was written to a file named A
.
The linked question asks if there is a way to represent this rename in the commit and the accepted answer correctly points out that commits know nothing of renames and rename information is computed on the fly when needed, e.g. during diffs or rebases.
Given a commit as described above, git diff
with default settings shows the change as a modification of A
and creation of a new file B
, no matter how much larger that makes the diff.
My questions are:
- Is there a way to tune
git diff
's settings so it understands the situation more accurately, as would be evidenced by a much smaller diff? - Failing that, what is the most idiomatic way of splitting up the commit into two separate commits, one for the
A
→B
rename* and one for the creation of the newA
(to at least assist a human reviewer with understanding what happened if they go through commits one by one)?
* Note that I'll use "a commit for the A
→ B
rename" as a shorthand for "a commit whose tree no longer contains A
but contains B
with the old contents of A
".
Some hints / things I've tried:
For the first question, some improvement can be gained using git diff
's -C
/--find-copies
switch. This will let it represent the change as A
having been copied to B
and then A
having been modified such that its contents are replaced with the new ones. This halves the size of the "unnecessary" parts of the diff, which are then just the removal of A
's "old contents". Still, it's not ideal.
For the second question, I guess the "brute force" way of doing it is:
- Reset
HEAD
back to before the commit in question while keeping the modifications in the working tree, or if it's not the latest commit, do an interactive rebase withe
at that commit, then resetHEAD
during the rebase. git add B
to addB
to the staging area.git rm --cached A
to add the removal ofA
to the staging area.git commit ...
those two changes, leavingA
as an unstaged file in the working tree.git add A
andgit commit ...
to commit the creation of the newA
.
This is quite cumbersome and I don't like that one has to leave A
as an unstaged file in the working tree between the two commits. But trying to do something "clever" like rebasing off a commit with only the A
→ B
rename or stashing the changed A
when redoing the commit manually makes matters worse, with Git's rename detection kicking in and in both cases just getting rid of A
altogether without warning. The most success I've had here is with rebasing using the old recursive
merge strategy and no-renames
option, which at least raises a conflict instead of silently getting rid of A
.