Age of git tracked file II
Goal
Identify the last time an arbitrary line (let's say line 1) was changed for every file in the repository.
Step 1
Try and use blame to identify changes to the first line only:
$ git blame -L 1,1 -- README ^193d722 (Paul Schwendenman 2012-07-23 16:51:03 +0200 1) ======================
Grab just the commit:
$ git ls-tree -r HEAD --name-only -z | nul_terminated head -n 2 | \ xargs -0n1 git blame -L 1,1 -- | cut -f1 -d" " f838b75f e5b94682
Step 2
Keep the file name and the commit id:
$ git ls-tree -r HEAD --name-only -z | nul_terminated head -n 2 | \ xargs -0n1 -I{} sh -c 'echo {}; git blame -L 1,1 -- {}' | paste - - -d, | \ cut -f1 -d" " .gitignore,f838b75f COPYING,e5b94682
Swap the order:
$ git ls-tree -r HEAD --name-only -z | nul_terminated head -n 2 | \ xargs -0n1 -I{} sh -c 'echo {}; git blame -L 1,1 -- {}' | paste - - -d, | \ cut -f1 -d" " | awk 'BEGIN{FS=",";OFS=",";} {print $2,$1}' f838b75f,.gitignore e5b94682,COPYING
Format the output like git arguments:
$ git ls-tree -r HEAD --name-only -z | nul_terminated head -n 2 | \ xargs -0n1 -I{} sh -c 'echo {}; git blame -L 1,1 -- {}' | paste - - -d, | \ cut -f1 -d" " | awk 'BEGIN{FS=",";OFS=",";} {print $2,$1}' | sed 's/,/ -- /' f838b75f -- .gitignore e5b94682 -- COPYING
Step 3
Rebuild the tools from last time:
$ git ls-tree -r HEAD --name-only -z | nul_terminated head -n 2 | \ xargs -0n1 -I{} sh -c 'echo {}; git blame -L 1,1 -- {}' | paste - - -d, | \ cut -f1 -d" " | awk 'BEGIN{FS=",";OFS=",";} {print $2,$1}' | sed 's/,/ -- /' | \ xargs -n3 -I{} sh -c 'echo {}; git log -n1 --format="%at:%ar" {}' | paste - - -d, f838b75f -- .gitignore,1398220033:1 year, 4 months ago e5b94682 -- COPYING,1347060526:3 years ago
Strip the crap in the front:
$ git ls-tree -r HEAD --name-only -z | nul_terminated head -n 2 | \ xargs -0n1 -I{} sh -c 'echo {}; git blame -L 1,1 -- {}' | paste - - -d, | \ cut -f1 -d" " | awk 'BEGIN{FS=",";OFS=",";} {print $2,$1}' | sed 's/,/ -- /' | \ xargs -n3 -I{} sh -c 'echo {}; git log -n1 --format="%at:%ar" {}' | \ paste - - -d: | cut -f 3- -d" " | sort -k 2 -t: COPYING:1347060526:3 years ago .gitignore:1398220033:1 year, 4 months ago
Final formatting:
$ git ls-tree -r HEAD --name-only -z | nul_terminated head -n 2 | \ xargs -0n1 -I{} sh -c 'echo {}; git blame -L 1,1 -- {}' | paste - - -d, | \ cut -f1 -d" " | awk 'BEGIN{FS=",";OFS=",";} {print $2,$1}' | sed 's/,/ -- /' | \ xargs -n3 -I{} sh -c 'echo {}; git log -n1 --format="%at:%ar" {}' | \ paste - - -d: | cut -f 3- -d" " | sort -k 2 -t: | cut -d: -f1,3- | column -t -s: COPYING 3 years ago .gitignore 1 year, 4 months ago
Debugging
Blame puts a ^ in front of the first commit which seems to break everything. That is a but dramatic. It breaks the git log look up and which generates no output. With no output the next command has the wrong number of arguments and that breaks everything. Use sed to remove that.
$ git blame -L 1,1 -- README | sed 's/\^//g' 193d722 (Paul Schwendenman 2012-07-23 16:51:03 +0200 1) ======================
Final
git ls-tree -r HEAD --name-only -z | xargs -0n1 -I{} sh -c 'echo {}; git blame -L 1,1 -- {}' | \ sed 's/\^//g' | paste - - -d, | cut -f1 -d" " | awk 'BEGIN{FS=",";OFS=",";} {print $2,$1}' | \ sed 's/,/ -- /' | xargs -n3 -I{} sh -c 'echo {}; git log -n1 --format="%at:%ar" {}' | \ paste - - -d: | cut -f 3- -d" " | sort -k 2 -t: | cut -d: -f1,3- | column -t -s:
Additional Note
The .gpg files are binary files and by default are unintelligible to git blame. However, you can configure git to use gpg to decrypt the files first.
git config --local blame.gpg.binary true git config --local blame.gpg.textconv "gpg -d --quiet --yes --compress-algo=none --no-encrypt-to"