8

Given a random file, is there a canonical method to determine from the command line whether the file belongs to a particular commit?

This is similar to stack overflow question find-out-which-git-commit-a-file-was-taken-from except that I wish to be able to use it in scripts and also not create a temporary branch.

Community
  • 1
  • 1
Xevious
  • 407
  • 4
  • 11
  • @Bryce the reason I ask is that (with git) I always suspect there is some command like "git ls-tree --do-something-magical that does exactly what I want. Like when I discovered "git hash-object" minutes after writing a handy git sha-1 calculator in Python! – Xevious Jul 27 '16 at 19:44
  • 1
    An example would be when someone sends a configuration file (retrieved from hardware) up the support tiers until it is handed off for a post mortem or diagnosis. What commit or release did it come from? In the wayback days, we used RCS (and eventually CVS) keyword expansion for this purpose. I am pretty sure we could use commit and checkout hooks to add fake keyword expansion, but it is such a kludge that I am a bit embarrassed to even mention it. – Xevious Jul 27 '16 at 20:04
  • 1
    The link suggests, and it's clear from your 2nd comment, that you're talking about a file that is not part of your working tree -- but the question itself doesn't say that. It sounds like you're just asking for `git log -1 -- $filename` (possibly with some confusion over the meaning of "belong"). Can you rephrase it a bit? – trent Jul 27 '16 at 22:35

4 Answers4

4

Below is an excerpt of a script that I have been using for the purpose. I hacked it together using my limited git knowledge and pieces of other scripts that I have found on the web. It works well enough, but I often find that there are easier ways to do things in git than what I have learned by trial and error.

FILE=$1

# git hash for file
HASH=`git hash-object $FILE`

# git revisions for file
REVS=`git log --pretty=%H -- $FILE`

# check each revision for checksum match
for rev in $REVS; do
    POSSIBLE=`git ls-tree $rev $FILE | awk '{print $3}'`
    if [[ $HASH == $POSSIBLE ]]; then
        echo $rev
    fi
done
Xevious
  • 407
  • 4
  • 11
  • 3
    Other than changing `git log --pretty=...` to `git rev-list --all -- $FILE` I would not change anything about your answer. – Bryce Drew Jul 27 '16 at 22:20
1

Do you mean, was the file modified in a commit? If so, something like git log --oneline -- filePathName should list the commits from HEAD where that is the case.

On second reading, I think you are just asking for commits which contain that file, whether or not its changed. If so, then doesn't your ls-tree need a -r flag, to recurse into its sub-trees (subdirs)? that will find any copies of a file under any name, if you just match on the sha.

David Neiss
  • 8,161
  • 2
  • 20
  • 21
  • 1
    That appears to return all commits involving that file name - but not the specific commit that contains the file in the exact state that it is in at the moment. – Xevious Jul 27 '16 at 19:50
  • ah, ok, so you are looking for an exact match of that file. So still, shouldnt you be recursing (above) and looking for a match of each of the blob's shas with the sha of the file you are trying to match to? – David Neiss Jul 27 '16 at 19:54
1

Your approach may fail to work in case of insignificant differences (e.g. line-ending style, or differences due to clean/smudge filters) between the local and repository versions of the file.

The following script works via git diff rather than relying on hashes. It accepts diff options after the file name.

Usage examples:

# list all commits that introduce the file README.md in its local state
list_introducing_commits README.md

# list all commits that introduce the file README.md in its local state
# ignoring any difference in whitespace
list_introducing_commits README.md -w

list_introducing_commits (couldn't find a better name):

#!/bin/bash

if [ $# -eq 0 ]
then
    echo "Usage: $(basename $0) path/to/file [<diff-options>]"
    exit 1
fi

file="$1"
shift 1

for rev in $(git log --pretty=%H -- "$file")
do
    if git diff --exit-code $@ $rev -- $file &> /dev/null
    then
        echo $rev
    fi
done
Community
  • 1
  • 1
Leon
  • 31,443
  • 4
  • 72
  • 97
1

Building on DavidN's answer, if the file is in the current worktree, and the worktree is in sync with HEAD, this will get you the commit corresponding to the file's current contents:

git log --pretty="%H" -1  -- path/to/file

But you might want to test those assumptions ahead of time via "git diff --exit-code /path/to/file" and taking a peek at $?.

Community
  • 1
  • 1
G. Sylvie Davies
  • 5,049
  • 3
  • 21
  • 30