Fez Vrasta
Fez Vrasta

Reputation: 14825

Get latest commit of list of files with Git

I'm trying to retrieve the most recent commit who modified/edited a list of files I provide.

In practice, if I have a.txt and b.txt, where a.txt has been modified yesterday and b.txt has been modified 2 hours ago, I want to get the latest commit which edited b.txt.

This is my script so far:

for file in components/*/index.jsx
do
  # 1. Get the dirname of the current file
  dir=`dirname $file`
  # 2. Get a space separated list of files to check in $dir
  files=`find $dir -type f | grep -E -v "demo.jsx|.test.jsx|meta.json|__snapshots__" | tr '\n' ' '`
  # 3. Get the most recent commit that touched any of the listed files
  commit=`git log -n 1 --pretty=format:%H -- $files | cat`
  # 4. Get the version of the package.json at the specific $commit
  version=`git show $commit:packages/react-components/package.json | cat | grep -m 1 version`
  # 5. Insert the version in the meta.json of the component
  sed -i'' -e "2s/.*/$version/" $dir/meta.json
  # 6. Remove the backup file generated by sed
  rm $dir/meta.json-e
done

The problem is in step #3, it doesn't return any commit at all if I provide multiple files.

This is the log of the problematic step:

❯ echo $files
components/Foobar/index.jsx components/Foobar/index.sss 

❯ git log -n 1 --pretty=format:%H -- $files | cat
# nothing

If instead I run:

❯ git log -n 1 --pretty=format:%H -- components/Foobar/index.jsx components/Foobar/index.sss | cat
93012738e776f4ffe920161ba61501ed84b815a5

I get the commit hash...

Ideas?

update: If I run:

`echo "git log -n 1 --pretty=format:%H -- $files"`

it prints the commit hash, why?

Upvotes: 1

Views: 1338

Answers (2)

tom
tom

Reputation: 22969

This extremely weird. Here are some more things to try.

Instructions:

  • Run the snippets separately one at a time by inserting a snippet into your script just before step 3 and running the script.
  • Copy and paste to avoid typos.
  • To prevent the for loop from running several times and producing lots of output, temporarily replace components/*/index.jsx with components/Foobar/index.jsx (or whichever file triggers the problem).
  • Don't do anything between theses test (like changing directory or running git commands).
  • If a snippet doesn't produce the expected output, say which snippet and what the output was.
  • Impatient? Try snippet 4 (which should succeed) and snippet 11 (which should fail). That's a sign that this is the right track and you should try all the steps.

Snippets:

  1. Check which shell is running the script

    echo "$SHELL"
    ls -l "$SHELL"
    

    Expected output: /bin/sh, which might be a symbolic link to bash or dash. If it's a symbolic link to another shell (e.g. zsh), that would be a problem because other shells expand variables differently.

  2. Double check that you don't have quotes (") around $files in the original command.

  3. Check that $IFS is sane.

    echo -n "$IFS" | xxd -p
    

    Expected output: 20090a (hex for <space><tab><newline>).
    (I know I've already asked you to do this, but please do it again from the script.)
    $IFS controls word splitting, and would cause problems if it was a strange value.

  4. Check that the files exist:

    ls components/Foobar/index.jsx components/Foobar/index.sss
    

    Expected output: ls should list the two files.
    If you get a "No such file or directory" error, check the working directory of the script at that point and the git branch that is checked out.

  5. Try using the files variable, set to a string:

    files="components/Foobar/index.jsx components/Foobar/index.sss"
    ls $files
    

    Expected output: ls should list the two files.

    If you get an error such as

    ls: cannot access components/Foobar/index.jsx components/Foobar/index.sss: No such file or directory
    

    then something is wrong with your shell, because it doesn't do word splitting on unquoted variables.

  6. Try running ls inside backticks.

    files="components/Foobar/index.jsx components/Foobar/index.sss"
    output=`ls $files`
    echo "$output"
    

    Expected output: The two files.
    If you get a "No such file or directory" error then your shell is broken and doesn't do variable expansion inside backticks properly.

  7. Use find instead of a literal string:

    ls $files
    

    Unlike snippet 5, this snippet allows $files to keep its value from step 2 in your script.
    Expected output: ls should list the same two files as the previous snippet.
    If you get an error (no such file or directory), something is wrong with step 2 of your script. Check the value of $files using echo -n "$files" | od -c and post it.

  8. Try the git command with literal file arguments:

    git log -n 1 --pretty=format:%H -- components/Foobar/index.jsx components/Foobar/index.sss | cat
    echo # output of previous command doesn't have a trailing newline
    

    Expected output: The commit hash.

  9. Try using the $files variable, set to a string:

    files="components/Foobar/index.jsx components/Foobar/index.sss"
    git log -n 1 --pretty=format:%H -- $files | cat
    echo # output of previous command doesn't have a trailing newline
    

    Expected output: The commit hash.

  10. Use find instead of a literal string:

    git log -n 1 --pretty=format:%H -- $files | cat
    echo # output of previous command doesn't have a trailing newline
    

    Expected output: The commit hash.

  11. Try the original command from the script:

    commit=`git log -n 1 --pretty=format:%H -- $files | cat`
    echo "$commit"
    

    Expected output: The commit hash.

Upvotes: 2

Fez Vrasta
Fez Vrasta

Reputation: 14825

Posting this as an answer, but it's mostly a workaround:

for file in components/*/index.jsx
do
  dir=`dirname $file`
  files=`find $dir -type f | grep -E -v "demo.jsx|meta.json|.test.jsx|__snapshots__" | tr '\n' ' '`
  # We basically print the command as string, and evaluate it with backtricks
  # Use only if you have complete control over the project structure!
  commit=`$(echo git log -n 1 --pretty=format:%H -- $files) | cat`
  version=`git show $commit:packages/react-components/package.json | cat | grep -m 1 version`
  sed -i'' -e "2s/.*/$version/" $dir/meta.json
  rm $dir/meta.json-e
done

I don't know why this is needed, but it seems to work at least.

Upvotes: 1

Related Questions