Back to the Tips Index

The vclog tool

Here is a perl script that is a mashup of git log and git diff to show differences between commits in your workspace.

If you use git, go to a directory in a repository and type git log to see a list of commits. You'd see something like this:

commit 63c0b2a0ac8ab1b23887be56ef8311dc02bdd1d0
Author: Howard Cohen 2 
Date:   Fri Oct 26 08:42:22 2012 -0700

    moved xyz123 and bbb345 info into abcde123456

commit 1defd35835d3e0501efbc395e03f14441e3b59e3
Author: Howard Cohen 2 
Date:   Tue Oct 23 16:03:48 2012 -0700

    added uvwxyz to catalog

commit 116bfc4fdde1a20de009da898576877fb8ac1413
Author: Howard Cohen 2 
Date:   Mon Oct 22 15:35:16 2012 -0700

    checkpointing publication fixes for abcde

commit 8529ac546a865827bbcf558938e03bbd833ee71f
Author: Howard Cohen 2 
Date:   Thu Oct 11 08:01:23 2012 -0700

    includes xyx server changes

commit 618ff1566c4c0a6786bb959bb61f50e76aa488b0
Author: Howard Cohen 2 
Date:   Mon Sep 17 08:02:02 2012 -0700

    Added the files we think we need to track from uni

It treats the list of commits shown by git log as a sequence.

It makes it really easy to compare files as they existed at the time of these commits. In other words, it can tell you what changed between the most recent commit and the current commit. Or between any two of the commits.

It does this by making it easy to refer to these commits using a shorthand notation.

You can refer to a commit by its sequence number when composing a vclog command line. It is a simple notation: -1 stands for the first commit before the HEAD. The HEAD will be the first item in git log's output. So, -1 is the second one in its output. In the above list it is the second commit, which has the object id 1defd35835d3e0501efbc395e03f14441e3b59e3. The next oldest commit would be -2 (the third item in the list) and -3 would be the fourth, and so on. If you pick too big of a number it just uses the last item in the list.

If you provide only one shorthand indicator (e.g. -1), the difference will be taken be using that commit and the HEAD. If you provide two shorthand indicators it will take the difference between those two commits.

You can list or more pathnames on the command line if you want. If you don't provide any it provides diffs for the entire repository.

You can pass -c if you'd like to simply see the the git diff command line.

For this command and the above git log output:

vclog -2 -3 somepathname
The diff command would look like this:
git diff 116bfc4fdde1a20de009da898576877fb8ac1413 8529ac546a865827bbcf558938e03bbd833ee71f somepathname

Installation

Put the contents of the below script into a file called "vclog" and then execute:
chmod +x vclog

Then you can execute the script by invoking it in Terminal (type its name and press return).


#!/usr/bin/perl
# vclog -- show the diffs for commit-by-commit changes
# Copyright Howard Cohen, 2012
#
# This code is released for use under the GNU General Public License version 2,
# which is the same license under which git is published.
#
# Usage: vclog [-c] [-d] [-from=fromver|fromver] [-to=tover|tover] [files...]
# 
# vclog first processes git log to produce a list of commits related to the
# current workspace.  It allows you to refer to them using shorthand, such as
#	-1 one commit behind the most recent one in the workspace.
#	-2 two commits behind the most recent one in the workspace.
#	-3 two commits behind the most recent one in the workspace.
#	and so on, up to the maximum number of commits listed by "git log"
#	-999 just take the last item (make sure it exceeds the # of commits)
#
# Examples:
# 	vclog -1			# shows diffs from version before HEAD
# 	vclog -from=-1			# same as "vclog -1"
# 	vclog -1 -2			# shows diffs from version before HEAD
#					# and the version right before that
# 	vclog -1 -c			# just show the git diff command line,
# 	vclog -1 somefile.pl 		# do "vclog -1" but only for somefile.pl
#					# there will be no diffs if 
#					#     somefile.pl had no changes 
#
# You can also use positive numbers instead of negative numbers.  No, it 
# doesn't write your code for you. Positive numbers are treated like their 
# negative equivalent.  For example:
#
# 	vclog 1
#
# is the same as:
#
# 	vclog -1
#


local $func='vclog';
local $wsname='';
local @products=();

local $debug=0;
local $verbose=0;
local $check=0;
local $errors=0;
local $fromver='';
local $tover='';
local @files=();

foreach my $arg ( @ARGV ) {
    if ( $arg eq '-d' ) {
        $debug=1;
    } elsif ( $arg eq '-v' ) {
        $verbose=1;
    } elsif ( $arg eq '-c' ) {
        $check=1;
    } elsif ( $arg =~ /^-from=([-\w]+)$/ ) {
        $fromver=$1;
    } elsif ( $arg =~ /^-to=([-\w]+)$/ ) {
        $tover=$1;
    } elsif ( $arg =~ /^([-\w\.\/]+)$/ ) {
	if ( $fromver eq '' && $arg =~ /^([-\w]+)$/ ) {
	    $fromver=$1;
	} elsif ( $tover eq '' && $arg =~ /^([-\w]+)$/ ) {
	    $tover=$1;
	} else {
	    push(@files, $arg);
	}
    } else {
        print STDERR "$func: unrecognized arg: $arg\n";
	exit 1;
    }
}

if ( $fromver eq '' ) {
    $fromver='-1';
}

local @commits=();
local %cinfo=();

if ( !open(GLOG, "/usr/bin/git log|")) {
    print STDERR "$func: cannot open pipe from git log. $!\n";
    exit 2;
} else {
    my $commit='';
    my @commentary=();
    my $i=0;
    while() {
        if ( /^commit\s+(\w+)/ ) {
	    $new_commit=$1;
	    $i++;
	    push(@commits, $new_commit);
	    print STDERR "$func: saw commit $i: $new_commit\n" if $debug;

	    if ( $commit ne '' ) {
	        $cinfo{$commit}=join('', @commentary);
		#print STDERR "$func: set cinfo{$commit}=$cinfo{$commit}\n"
		#	if $debug;
	    }

	    $commit=$new_commit;
	    @commentary=();
	} else {
	    push(@commentary, $_);
	}
    }

    if ( $commit ne '' && $#commentary > -1 ) {
	$cinfo{$commit}=join('', @commentary);
    }

    close(GLOG);
}

my $fromobj = getobjid($fromver);
my $toobj = getobjid($tover);

if ( $fromver ne '' ) {
    print STDERR"From: $fromver ($fromobj)\n";
    print STDERR$cinfo{$fromobj};
}

if ( $tover ne '' ) {
    print STDERR"Thru: $tover ($toobj)\n";
    print STDERR$cinfo{$toobj};
}

my $cmdline="/usr/bin/git diff  $fromobj $toobj";
if ( $#files > -1 ) {
    $cmdline .= ' ' . join(" ", @files);
}

print STDERR"$func: cmdline=$cmdline\n" if $check or $debug;
system($cmdline) unless $check;

sub getobjid {
    my($ver)=@_;
    my $sfunc='getobjid';
    my $result='';

    print STDERR "$sfunc: ------------------------\n" if $debug;
    print STDERR "$sfunc: ver=$ver\n" if $debug;

    my $max=$#commits;
    my $vismax=$max+1;
    print STDERR "$sfunc: max=$max\n" if $debug;

    # the minus sign is optional
    if ( $ver =~ /^-?(\d+)/ ) {
	my $rootver=$1;
        if ( $rootver >= $max ) {
	    print STDERR
	      "$func: Log only goes back $vismax entries -- using last entry\n";
	    $rootver=$max;
	}

	$result=$commits[$rootver];
    } elsif ( $ver =~ /head/i ) {
    } elsif ( $ver =~ /prev/i ) {
	$result=$commits[1] if $max >=1;
    } else {
	print STDERR
	    "$sfunc: didn't recognize shorthand ver='$ver', using as-is\n"
	    if $debug;
	$result=$ver;
    }

    print STDERR "$sfunc: returning result=$result\n" if $debug;
    return $result;
}

All content copyright Howard Cohen 2012, all rights reserved worlwide.
hoco(at)timefold(dot)com