Age of git tracked file

DateReadtime 5 minutes Series Part 1 of Age of git tracked file Tags

Goal

I use a password store named pass. Pass is a command line based manager that attempts to follow the Unix philosophy (roughly tools should do one thing and do it well.) I wanted to know when the last time I updated each password in my store, so that I could update some of the older passwords. pass optionally uses git to track changes to the password store. So basically, for each git tracked file I need the date of the last commit that changed that file.

Part 1

Figuring out how to list all of the files was relatively easy. git has a nice built in subcommand:

$  git ls-tree HEAD
100644 blob f97b7219a54a68b39dc6092342f20335da416c97        .gitattributes
100644 blob 78210c8c29ad35298a1cf612346db78ec4fbb5c9        .gpg-id
040000 tree b065b92bdf7c6ef1a18eb2072c2342336b354cf9        Personal
040000 tree b066a202ce90e3d6c649bc523442703ee4ff0220        Work

With some quick arguments tweaks you can echo all the files like this:

git ls-tree -r HEAD --name-only -z | xargs -0 -n1 echo

Part 2

Next we need to figure out how to display the date a file was modified

By using git log and a custom format. e.g.

ISO 8601:

$ git log -n1 --format="%ai"
2015-08-29 17:32:56 -0400

Unix time stamp:

$  git log -n1 --format="%at"
1422463382

Relative date:

$  git log -n1 --format="%ar"
21 hours ago

Part 3

Now lets try and put the two together:

$  git ls-tree -r HEAD --name-only -z | xargs -0 -n1 git log -n1 --format="%ai" --
2014-04-23 04:27:13 +0200
2012-09-08 01:28:46 +0200
2014-04-24 22:38:38 +0200
2014-12-04 21:07:40 +0800

As you can see the output only includes the date without the name of the file. From what I can tell there isn't a way to get the file name in the format line.

At this point I needed to play with the output and I only wanted three lines rather than the 40 I was getting. But head doesn't handle '0' lines cleanly. Rather than removing the nul separation I found a function on stackexchange that helps with that issue:

nul_terminated() {
  tr '\0\n' '\n\0' | "$@" | tr '\0\n' '\n\0'
}

Which allows:

find ... -print0 | nul_terminated tail -n 12 | xargs -r0 ...

Part 4

In order to get both the file name and the date to show up at the same time I am going to use sh to run two commands.

$ git ls-tree -r HEAD --name-only -z | nul_terminated head -n 2 | \
  xargs -0 -n1 -I{} sh -c 'echo {}; git log -n1 --format="%ai" -- {}'
.gitattributes
2014-05-16 14:16:49 -0400
.gpg-id
2014-01-27 13:40:45 -0500

Next, use paste to combine each pair of lines:

$ git ls-tree -r HEAD --name-only -z | nul_terminated head -n 2 | \
  xargs -0 -n1 -I{} sh -c 'echo {}; git log -n1 --format="%ai" -- {}' | paste - - -d,
.gitattributes,2014-05-16 14:16:49 -0400
.gpg-id,2014-01-27 13:40:45 -0500

Part 5

The output of this should be sorted by time:

$ git ls-tree -r HEAD --name-only -z | nul_terminated head -n 2 | \
  xargs -0 -n1 -I{} sh -c 'echo {}; git log -n1 --format="%at %ai" -- {}' | paste - - -d" " | sort -k 2
.gpg-id 1390848045 2014-01-27 13:40:45 -0500
.gitattributes 1400264209 2014-05-16 14:16:49 -0400

In the piece above, the UNIX time stamp was added and sort used it as a key. Next, use cut to remove the UNIX time stamp column:

$ git ls-tree -r HEAD --name-only -z | nul_terminated head -n 2 | \
  xargs -0 -n1 -I{} sh -c 'echo {}; git log -n1 --format="%at %ai" -- {}' | \
  paste - - -d" " | sort -k 2 | cut -d" " -f1,3-
.gpg-id 2014-01-27 13:40:45 -0500
.gitattributes 2014-05-16 14:16:49 -0400

Part 6

Next, I am am going to play with the output and see if I can get something more readable.

Now the output could be placed in a table using column:

$  git ls-tree -r HEAD --name-only -z | nul_terminated head -n 2 | \
   xargs -0 -n1 -I{} sh -c 'echo {}; git log -n1 --format="%at %ai" -- {}' | \
   paste - - -d" " | sort -k 2 | cut -d" " -f1,3- | column -t
.gpg-id         2014-01-27  13:40:45  -0500
.gitattributes  2014-05-16  14:16:49  -0400

Swap the ISO date with a relative date:

$  git ls-tree -r HEAD --name-only -z | nul_terminated head -n 2 | \
   xargs -0 -n1 -I{} sh -c 'echo {}; git log -n1 --format="%at %ar" -- {}' | \
   paste - - -d" " | sort -k 2 | cut -d" " -f1,3- | column -t
.gpg-id         1  year,  7  months  ago
.gitattributes  1  year,  3  months  ago

There are two many columns in the previous table output, switch so only the date is aligned after the file name.

$  git ls-tree -r HEAD --name-only -z | nul_terminated head -n 2 | \
   xargs -0 -n1 -I{} sh -c 'echo {}; git log -n1 --format="%at:%ar" -- {}' | \
   paste - - -d: | sort -k 2 -t: | cut -d: -f1,3- | column -t -s:
.gpg-id         1 year, 7 months ago
.gitattributes  1 year, 3 months ago

Final Command

$  git ls-tree -r HEAD --name-only -z | xargs -0 -n1 -I{} sh -c 'echo {}; \
   git log -n1 --format="%at:%ar" -- {}' | paste - - -d: | sort -k 2 -t: | \
   cut -d: -f1,3- | column -t -s:
.gpg-id         1 year, 7 months ago
.gitattributes  1 year, 3 months ago