Age of git tracked file
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