X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/blobdiff_plain/524de4db2639d37aa7049de4363c5d482cd34a0e..99b402cd622d38cb8337498d8fdb4f135e8be03c:/IkiWiki/Plugin/cvs.pm diff --git a/IkiWiki/Plugin/cvs.pm b/IkiWiki/Plugin/cvs.pm index 076af26f3..26a3e9dd2 100644 --- a/IkiWiki/Plugin/cvs.pm +++ b/IkiWiki/Plugin/cvs.pm @@ -1,12 +1,42 @@ -#!/usr/pkg/bin/perl +#!/usr/bin/perl package IkiWiki::Plugin::cvs; +# Copyright (c) 2009 Amitai Schlair +# All rights reserved. +# +# This code is derived from software contributed to ikiwiki +# by Amitai Schlair. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY IKIWIKI AND CONTRIBUTORS ``AS IS'' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION +# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + use warnings; use strict; use IkiWiki; +use File::chdir; + sub import { - hook(type => "getopt", id => "cvs", call => \&getopt); + hook(type => "genwrapper", id => "cvs", call => \&genwrapper); hook(type => "checkconfig", id => "cvs", call => \&checkconfig); hook(type => "getsetup", id => "cvs", call => \&getsetup); hook(type => "rcs", id => "rcs_update", call => \&rcs_update); @@ -21,11 +51,15 @@ sub import { hook(type => "rcs", id => "rcs_getctime", call => \&rcs_getctime); } -sub getopt () { - # "cvs add dir" acts immediately on the repository. - # post-commit gets confused by this and doesn't need to act on it. - # If that's why we're here, terminate the process. - @ARGV == 3 && $ARGV[1] eq "NONE" && $ARGV[2] eq "NONE" && exit 0; +sub genwrapper () { + return < { safe => 0, # rcs plugin rebuild => undef, + section => "rcs", }, cvsrepo => { type => "string", @@ -69,7 +104,7 @@ sub getsetup () { cvs_wrapper => { type => "string", example => "/cvs/wikirepo/CVSROOT/post-commit", - description => "cvs post-commit hook to generate (triggered by CVSROOT/loginfo entry", + description => "cvs post-commit hook to generate (triggered by CVSROOT/loginfo entry)", safe => 0, # file rebuild => 0, }, @@ -100,7 +135,7 @@ sub cvs_info ($$) { my $field=shift; my $file=shift; - chdir $config{srcdir} || error("Cannot chdir to $config{srcdir}: $!"); + local $CWD = $config{srcdir}; my $info=`cvs status $file`; my ($ret)=$info=~/^\s*$field:\s*(\S+)/m; @@ -108,36 +143,17 @@ sub cvs_info ($$) { } sub cvs_runcvs(@) { - my ($cmd) = @_; - unshift @$cmd, 'cvs', '-Q'; - - eval q{ - use IPC::Cmd; - }; - error($@) if $@; - - chdir $config{srcdir} || error("Cannot chdir to $config{srcdir}: $!"); - - debug("runcvs: " . join(" ", @$cmd)); - - my ($success, $error_code, $full_buf, $stdout_buf, $stderr_buf) = - IPC::Cmd::run(command => $cmd, verbose => 0); - if (! $success) { - warn(join(" ", @$cmd) . " exited with code $error_code\n"); - warn(join "", @$stderr_buf); - } - return $success; -} + my @cmd = @_; + unshift @cmd, 'cvs', '-Q'; -sub cvs_shquote_commit ($) { - my $message = shift; + local $CWD = $config{srcdir}; - eval q{ - use String::ShellQuote; - }; - error($@) if $@; + open(my $savedout, ">&STDOUT"); + open(STDOUT, ">", "/dev/null"); + my $ret = system(@cmd); + open(STDOUT, ">&", $savedout); - return shell_quote(IkiWiki::possibly_foolish_untaint($message)); + return ($ret == 0) ? 1 : 0; } sub cvs_is_controlling { @@ -148,7 +164,7 @@ sub cvs_is_controlling { sub rcs_update () { return unless cvs_is_controlling; - cvs_runcvs(['update', '-dP']); + cvs_runcvs('update', '-dP'); } sub rcs_prepedit ($) { @@ -192,13 +208,14 @@ sub rcs_commit ($$$;$$) { if (defined $rev && defined $oldrev && $rev != $oldrev) { # Merge their changes into the file that we've # changed. - cvs_runcvs(['update', $file]) || + cvs_runcvs('update', $file) || warn("cvs merge from $oldrev to $rev failed\n"); } - if (! cvs_runcvs(['commit', '-m', cvs_shquote_commit $message])) { + if (! cvs_runcvs('commit', '-m', + IkiWiki::possibly_foolish_untaint $message)) { my $conflict=readfile("$config{srcdir}/$file"); - cvs_runcvs(['update', '-C', $file]) || + cvs_runcvs('update', '-C', $file) || warn("cvs revert failed\n"); return $conflict; } @@ -218,7 +235,8 @@ sub rcs_commit_staged ($$$) { $message="web commit from $ipaddr".(length $message ? ": $message" : ""); } - if (! cvs_runcvs(['commit', '-m', cvs_shquote_commit $message])) { + if (! cvs_runcvs('commit', '-m', + IkiWiki::possibly_foolish_untaint $message)) { warn "cvs staged commit failed\n"; return 1; # failure } @@ -231,14 +249,32 @@ sub rcs_add ($) { my $parent=IkiWiki::dirname($file); my @files_to_add = ($file); + eval q{use File::MimeInfo}; + error($@) if $@; + until ((length($parent) == 0) || cvs_is_controlling("$config{srcdir}/$parent")){ push @files_to_add, $parent; $parent = IkiWiki::dirname($parent); } while ($file = pop @files_to_add) { - cvs_runcvs(['add', $file]) || - warn("cvs add $file failed\n"); + if (@files_to_add == 0) { + # file + my $filemime = File::MimeInfo::default($file); + if (defined($filemime) && $filemime eq 'text/plain') { + cvs_runcvs('add', $file) || + warn("cvs add $file failed\n"); + } + else { + cvs_runcvs('add', '-kb', $file) || + warn("cvs add binary $file failed\n"); + } + } + else { + # directory + cvs_runcvs('add', $file) || + warn("cvs add $file failed\n"); + } } } @@ -248,7 +284,7 @@ sub rcs_remove ($) { return unless cvs_is_controlling; - cvs_runcvs(['rm', '-f', $file]) || + cvs_runcvs('rm', '-f', $file) || warn("cvs rm $file failed\n"); } @@ -258,7 +294,7 @@ sub rcs_rename ($$) { return unless cvs_is_controlling; - chdir $config{srcdir} || error("Cannot chdir to $config{srcdir}: $!"); + local $CWD = $config{srcdir}; if (system("mv", "$src", "$dest") != 0) { warn("filesystem rename failed\n"); @@ -274,18 +310,32 @@ sub rcs_recentchanges($) { return unless cvs_is_controlling; - eval q{ - use Date::Parse; - }; + eval q{use Date::Parse}; error($@) if $@; - chdir $config{srcdir} || error("Cannot chdir to $config{srcdir}: $!"); + local $CWD = $config{srcdir}; + + # There's no cvsps option to get the last N changesets. + # Write full output to a temp file and read backwards. + + eval q{use File::Temp qw/tempfile/}; + error($@) if $@; + eval q{use File::ReadBackwards}; + error($@) if $@; - open CVSPS, "env TZ=UTC cvsps -q --cvs-direct -z 30 -x |" || error "couldn't get cvsps output: $!\n"; - my @spsvc = reverse ; # is this great? no it is not - close CVSPS || error "couldn't close cvsps output: $!\n"; + my ($tmphandle, $tmpfile) = tempfile(); + system("env TZ=UTC cvsps -q --cvs-direct -z 30 -x >$tmpfile"); + if ($? == -1) { + error "couldn't run cvsps: $!\n"; + } + elsif (($? >> 8) != 0) { + error "cvsps exited " . ($? >> 8) . ": $!\n"; + } - while (my $line = shift @spsvc) { + tie(*SPSVC, 'File::ReadBackwards', $tmpfile) + || error "couldn't open $tmpfile for read: $!\n"; + + while (my $line = ) { $line =~ /^$/ || error "expected blank line, got $line"; my ($rev, $user, $committype, $when); @@ -305,7 +355,7 @@ sub rcs_recentchanges($) { # @pages (and revisions) # - while ($line = shift @spsvc) { + while ($line = ) { last if ($line =~ /^Members:/); for ($line) { s/^\s+//; @@ -325,7 +375,7 @@ sub rcs_recentchanges($) { } if length $page; } - while ($line = shift @spsvc) { + while ($line = ) { last if ($line =~ /^Log:$/); chomp $line; unshift @message, { line => $line }; @@ -335,35 +385,39 @@ sub rcs_recentchanges($) { $message[0]->{line}=~/$config{web_commit_regexp}/) { $user=defined $2 ? "$2" : "$3"; $message[0]->{line}=$4; - } else { + } + else { $committype="cvs"; } - $line = shift @spsvc; # Tag - $line = shift @spsvc; # Branch + $line = ; # Tag + $line = ; # Branch - $line = shift @spsvc; + $line = ; if ($line =~ /^Author: (.*)$/) { $user = $1 unless defined $user && length $user; - } else { + } + else { error "expected Author, got $line"; } - $line = shift @spsvc; + $line = ; if ($line =~ /^Date: (.*)$/) { $when = str2time($1, 'UTC'); - } else { + } + else { error "expected Date, got $line"; } - $line = shift @spsvc; + $line = ; if ($line =~ /^PatchSet (.*)$/) { $rev = $1; - } else { + } + else { error "expected PatchSet, got $line"; } - $line = shift @spsvc; # --------------------- + $line = ; # --------------------- push @ret, { rev => $rev, @@ -373,16 +427,18 @@ sub rcs_recentchanges($) { message => [@message], pages => [@pages], } if @pages; - return @ret if @ret >= $num; + last if @ret >= $num; } + unlink($tmpfile) || error "couldn't unlink $tmpfile: $!\n"; + return @ret; } sub rcs_diff ($) { my $rev=IkiWiki::possibly_foolish_untaint(int(shift)); - chdir $config{srcdir} || error("Cannot chdir to $config{srcdir}: $!"); + local $CWD = $config{srcdir}; # diff output is unavoidably preceded by the cvsps PatchSet entry my @cvsps = `env TZ=UTC cvsps -q --cvs-direct -z 30 -g -s $rev`; @@ -395,7 +451,8 @@ sub rcs_diff ($) { if (wantarray) { return @cvsps; - } else { + } + else { return join("", @cvsps); } }