None of the git show
suggestions truly satisfy because (try as I might), I can not find a way not to get the metadata cruft from the top of the output. The spirit of cat(1) is just to show the contents.
This (below) takes a file name and an optional number. The number is how commits you want to go back. (Commits that changed that file. Commits that do not change the target file are not counted.)
gitcat.pl filename.txt
gitcat.pl -3 filename.txt
shows the contents of filename.txt as of the latest commit of filename.txt and the contents from 3 commits before that.
#!/usr/bin/perl -w
use strict;
use warnings;
use FileHandle;
use Cwd;
# Have I mentioned lately how much I despise git?
(my $prog = $0) =~ s!.*/!!;
my $usage = "Usage: $prog [revisions-ago] filename\n";
die( $usage ) if( ! @ARGV );
my( $revision, $fname ) = @ARGV;
if( ! $fname && -f $revision ) {
( $fname, $revision ) = ( $revision, 0 );
}
gitcat( $fname, $revision );
sub gitcat {
my( $fname, $revision ) = @_;
my $rev = $revision;
my $file = FileHandle->new( "git log --format=oneline '$fname' |" );
# Get the $revisionth line from the log.
my $line;
for( 0..$revision ) {
$line = $file->getline();
}
die( "Could not get line $revision from the log for $fname.\n" )
if( ! $line );
# Get the hash from that.
my $hash = substr( $line, 0, 40 );
if( ! $hash =~ m/ ^ ( [0-9a-fA-F]{40} )/x ) {
die( "The commit hash does not look a hash.\n" );
}
# Git needs the path from the root of the repo to the file because it can
# not work out the path itself.
my $path = pathhere();
if( ! $path ) {
die( "Could not find the git repository.\n" );
}
exec( "git cat-file blob $hash:$path/'$fname'" );
}
# Get the path from the git repo to the current dir.
sub pathhere {
my $cwd = getcwd();
my @cwd = split( '/', $cwd );
my @path;
while( ! -d "$cwd/.git" ) {
my $path = pop( @cwd );
unshift( @path, $path );
if( ! @cwd ) {
die( "Did not find .git in or above your pwd.\n" );
}
$cwd = join( '/', @cwd );
}
return join( '/', map { "'$_'"; } @path );
}