11

I'm writing a post-commit hook for git. I would like to know if the latest commit changed any of the files in a particular directory. If there were any changes I can then proceed and call some expensive code, otherwise I can just skip it.

So, far I'm getting the short hash like so:

# get last commit hash prepended with @ (i.e. @8a323d0)
function parse_git_hash() {
  git rev-parse --short HEAD 2> /dev/null | sed "s/\(.*\)/@\1/"
}

Now, I need to determine if there were any changes to the specified directory. But I'm not sure how to go about that. I've looked at and played with git-log and git-show but without success so far.

So, I need to do something like this.

if [directory-changed()] {
  echo "start expensive operation"
}

This actually gets me part way there: Actually it grabs the last commit not the one specified.

git log  -U a79851b -1 my/directory/path

Thanks in advance.

Jon49
  • 4,444
  • 4
  • 36
  • 73

2 Answers2

13

You can get the added, modified, deleted and renamed files in the latest commit with:

git diff --name-only --diff-filter=AMDR --cached @~..@

To get the changes affecting a specific directory, filter the output using grep. For example:

changes() {
  git diff --name-only --diff-filter=AMDR --cached @~..@
}

if changes | grep -q dirname {
  echo "start expensive operation"
}
janos
  • 120,954
  • 29
  • 226
  • 236
  • I think `changes` isn't outputting anything since I'm doing this in post commit so there are no changes to diff. I'll have to figure out how to get the second oldest commit to compare against. – Jon49 Jul 12 '17 at 21:12
  • Hm, my bad, this works well in `pre-commit` hooks, but I see now that you need post-commit. What if you stick a `^` at the end, that is: `git diff --name-only --diff-filter=AMR --cached HEAD^` – janos Jul 12 '17 at 21:15
  • 4
    This worked: `git diff --name-only --diff-filter=ADMR @~..@` – Jon49 Jul 12 '17 at 21:17
  • @Jon49 hm, I'd like to add `@~..@` to my answer if I knew what it actually meant... – janos Jul 12 '17 at 21:21
  • I got it from https://stackoverflow.com/a/9903611/632495 in the comments I noticed that it said on Windows I need to be using `~`. So, your answer as is works. I learned something new today, `~` not `^` on Windows :-) . I actually prefer `HEAD^` since it is more self explanatory. – Jon49 Jul 12 '17 at 21:24
  • 1
    @Jon49 oh I see, yes, `^` is the equivalent of `~1`, but only the latter works in windows, but I hardly ever use windows so I'm not used to it. Thanks for the link! – janos Jul 12 '17 at 21:29
  • if you want to inspect just a specific folder/file you can use git diff on just that, by adding the folder(s)/files as argument `git diff --name-only --diff-filter=AMDR --cached @~..@ dir1/ dir2/ dir3/subdir/ file2.txt`. This also could be improved with using `--exit-code`. – luckydonald Jan 04 '19 at 17:29
  • 2
    Dropping the `--cached` worked for me (ran on OSX, Linux and pipeline). I ended up using `git diff --name-only --diff-filter=ADMR @~..@ ./$BASE_DIR/$SERVICE_NAME` which will be saving me a lot of valuable build minutes! – leeman24 Sep 12 '19 at 16:36
  • What's the purpose of `@~..@`? – Tiago Apr 12 '22 at 12:31
1

A modified version of the script with arguments would be:

#!/bin/bash

git diff --name-only --diff-filter=ADMR @~..@ | grep -q $1
retVal=$?

echo "git command retVal : ${retVal}"
if [ $retVal -eq 0 ]; then
  echo "folder/file : $1 changed"
else
  echo "no match found for the folder/file : $1"
  exit $retVal
fi
Tinkaal Gogoi
  • 4,344
  • 4
  • 27
  • 36