#!/usr/bin/perl -w ########################################### # noworries - Developing with a safety net # Mike Schilli, 2005 (m@perlmeister.com) ########################################### use strict; use Sysadm::Install qw(:all); use File::Find; use SGI::FAM; use Log::Log4perl qw(:easy); use File::Basename; use Getopt::Std; use File::Spec::Functions qw(rel2abs abs2rel); use DateTime; use DateTime::Format::Strptime; use Pod::Usage; my $RCS_DIR = "$ENV{HOME}/.noworries.rcs"; my $SAFE_DIR = "$ENV{HOME}/noworries"; my $CI = "ci"; my $CO = "co"; my $RLOG = "rlog"; getopts("dr:wl", \my %opts); mkd $RCS_DIR unless -d $RCS_DIR; Log::Log4perl->easy_init({ category => 'main', level => $opts{d} ? $DEBUG : $INFO, file => $opts{w} && !$opts{d} ? "/tmp/noworries.log" : "stdout", layout => "%d %p %m%n" }); if($opts{w}) { INFO "$0 starting up"; watcher(); } elsif($opts{r} or $opts{l}) { my($file) = @ARGV; pod2usage("No file given") unless defined $file; my $filename = basename $file; my $absfile = rel2abs($file); my $relfile = abs2rel($absfile, $SAFE_DIR); my $reldir = dirname($relfile); cd "$RCS_DIR/$reldir"; if($opts{l}) { rlog($filename); } else { sysrun("co", "-r$opts{r}", "-p", $filename); } cdback; } else { pod2usage("No valid option given"); } ########################################### sub watcher { ########################################### cd $SAFE_DIR; my $fam = SGI::FAM->new(); watch_subdirs(".", $fam); while (1) { # Block until next event my $event=$fam->next_event(); my $dir = $fam->which($event); my $fullpath = $dir . "/" . $event->filename(); # Emacs temp files next if $fullpath =~ /~$/; # Vi temp files next if $fullpath =~ /\.sw[px]x?$/; DEBUG "Event: ", $event->type, "(", $event->filename, ")"; if($event->type eq "create" and -d $fullpath) { DEBUG "Dynamically adding monitor ", "for directory $fullpath\n"; $fam->monitor($fullpath); } elsif($event->type =~ /create|change/ and -f $fullpath) { check_in($fullpath); } } } ########################################### sub watch_subdirs { ########################################### my($start_dir, $fam) = @_; $fam->monitor($start_dir); for my $dir (subdirs($start_dir)) { DEBUG "Adding monitor for $dir"; $fam->monitor($dir); } return $fam; } ########################################### sub subdirs { ########################################### my($dir) = @_; my @dirs = (); find sub { return unless -d; return if /^\.\.?$/; push @dirs, $File::Find::name; }, $dir; return @dirs; } ########################################### sub check_in { ########################################### my ($file) = @_; if(! -T $file) { DEBUG "Skipping non-text file $file"; return; } my $rel_dir = dirname($file); my $rcs_dir = "$RCS_DIR/$rel_dir/RCS"; mkd $rcs_dir unless -d $rcs_dir; cd "$RCS_DIR/$rel_dir"; cp "$SAFE_DIR/$file", "."; my $filename = basename($file); INFO "Checking $filename into RCS"; my ($stdout, $stderr, $exit_code) = tap($CI, "-t-", "-m-", $filename); INFO "Check-in result: ", "rc=$exit_code $stdout $stderr"; ($stdout, $stderr, $exit_code) = tap($CO, "-l", $filename); cdback; } ########################################### sub time_diff { ########################################### my ($dt) = @_; my $dur = DateTime->now() - $dt; for(qw(weeks days hours minutes seconds)) { my $u = $dur->in_units($_); return "$u $_" if $u; } } ########################################### sub rlog { ########################################### my ($file) = @_; my ($stdout, $stderr, $exit_code) = tap($RLOG, $file); my $p = DateTime::Format::Strptime->new( pattern => '%Y/%m/%d %H:%M:%S'); while($stdout =~ /^revision\s(\S+).*? date:\s(.*?); (.*?)$/gmxs) { my($rev, $date, $rest) = ($1, $2, $3); (my $lines) = ($rest =~ /lines:\s+(.*)/); $lines ||= "first version"; my $dt = $p->parse_datetime($date); print "$rev ", time_diff($dt), " ago ($lines)\n"; } } __END__ =head1 NAME noworries - Developing with a safety net =head1 SYNOPSIS # Print previous version noworries -r revision file # C # List all revisions noworries -l file noworries -w # Start the watcher