languitar
languitar

Reputation: 6784

git log --name-status show new name of single file on rename

When using git log --name-status -- <filename>, how can you display the new name of a file that was renamed in a commit? --name-status only seems to display the full rename operation in case no specific filename is given on the command line:

Here is an example from one of my repos. With the provided filename, I only see a deletion, but not the new filename:

❯ git log --name-status --oneline -- setup.py
f4b8f93 Use uwsgi.ini and remove legacy runserver stuff
A       setup.py
5bb21a8 Rename setup.py to setup_pymake.py
D       setup.py

Using --name-status without a filename gives the full details:

❯ git log --name-status --oneline 5bb21a8
5bb21a8 Rename setup.py to setup_pymake.py
R100    setup.py        setup_pymake.py

I'd like to have this kind of information also when tracing a single file.


Edit: this specific case seem to appear only in case the commit order is as follows (from oldest to newest):

  1. create setup.py
  2. rename setup.py -> setup_pymake.py
  3. create an additional setup.py

Without the last step, this strange behavior doesn't exist. I am not sure whether I actually find it reasonable that git backtracks from commit 3 to 2.


Edit 2: --follow doesn't change anything here as the original issues seems to be that there is an "unwanted" following.

Upvotes: 4

Views: 474

Answers (1)

jthill
jthill

Reputation: 60275

Okay, the new question edit shows the misunderstanding here: if you want git to follow changes to a particular set of content you're interested in, you have to point git at that content. What you've got is a set of content added at setup.py, then that content renamed elsewhere, then a completely new set of content added at setup.py, and you want to follow the history of what happened to the setup.py that was there before, not the setup.py that's there now.

To find "the setup.py that was there before",

oldsetup=`git log -1 --pretty=%h --diff-filter=D -- setup.py`

will set oldsetup to just the long-enough hash of the most recent commit that deleted setup.py, and you can trace that content's history with

git log $oldsetup --oneline --name-status --follow -- setup.py

edit 3: ... aaaannnd that doesn't show the final rename destination. To get what you want, following both rename-from and rename-to all content that ever lived at a path, you're going to have to build your own path tracer that works off the all-paths --name-status output. To do it manually, find all the additions and deletions and show the fully-inspected path status at each,

threads=`git log --diff-filter=ADRC --pretty=%h -- path` 

git log --no-walk --name-status --oneline $threads

and when you automate it, start from there. The --name-status output is digested for human consumption, for scripting you "should" use the --raw output.


If what you want is to show what happened to every set of content that was ever at setup.py, you'll have to get good with sed or anything beefy enough to do that job, see below, and you might want to look at the --raw option instead of --name-status to help you trace all the threads you're trying to collect here.


The rename detection turns on Git checking all the other changes against changes to each path, but specifying a path turns off Git checking anything but changes to that path. It so happens that the path spec came in last and shut off the check-everything-else logic.

What you're after is Git looking at everything and then showing you everything about the changes that affect that path. It doesn't come with that built in, but it's not hard to pull what you need from the full output once you know the idiom for getting sed to work with line gaggles.

Here's the command to do what you want,

git log --name-status -m --oneline \
| sed '/^[A-Z]/{/\tsetup\.py/!d;H;$!d};x;/\tsetup\.py/!d'

which is admittedly not more than | | that far from pure line noise, for the uninitiated, but here's your initiation. It's not actually that hard.

sed has two buffers, the "pattern" buffer and the "hold" buffer. The sed script above works by accumulating a line gaggle in the hold buffer, then when it sees eof or the start of the next gaggle, it swaps that for the accumulated gaggle and prints only the interesting ones.

Sed script commands are separated by ;s or newlines (older seds don't speak semicolons and demand newline separators), can be grouped with {/} pairs, and are run on each line reaching them¹. The operating commands are one-letter affairs, p to print, d to delete, like that; they can be preceded by a pattern or range to select whether they care about a line. So /^[A-Z]/{…} says the sed commands in the braces operate on lines starting A..Z. and the initiated understand that /continuation/{…H;$!d};x is the accumulate-a-line-gaggle idiom. When sed's execution gets past the x command, the pattern buffer has an entire gaggle to work with.

To try it with something a little simpler, here's an implementation of git log --grep:

git log \
| sed '/^commit /!{H;$!d};x;/pattern/!d'

If a line doesn't start commit it's a continuation line, it's appended to the hold buffer (H) and if it's not the last line in the file ($!, that's a line-selection pattern just like /^commit /!) pattern processing's done, d says don't do anything more with this buffer, run the script again on the next line (but with the hold buffer preserved). So if execution reaches the x we either have a commit line or the last line of the very last commit message. x swaps the hold buffer and the pattern buffer. The pattern buffer now has the first line of the previous commit and all its appended following lines. If that entire gaggle doesn't include the pattern we want, it's dropped, but all the gaggles we do want are printed.

The command you want only includes the header line and any change lines that affect setup.py,

/^[A-Z]/{/\tsetup\.py/!d

and only prints the commits that contain such a change line, /\tsetup.py/!d at the end.


The -m option tells git log to display the differences from each parent for merge commits; normally it doesn't show changes that were trivially merged (since those changes originally showed up in a commit that's going to get listed soon anyway). I included it here mostly to show it's there.

¹sed's default loop is to load each line sequentially into the pattern buffer and run the whole script; if the script doesn't drop the buffer or otherwise avoid reaching the end of the script it prints whatever's in the buffer at the end.

Upvotes: 1

Related Questions