X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/blobdiff_plain/bb2697ca5e39bc40a18aa01582087cc5c558442e..2fedc701c9eb0d34c229ed0532112d35b593e561:/IkiWiki/Plugin/cvs.pm?ds=inline diff --git a/IkiWiki/Plugin/cvs.pm b/IkiWiki/Plugin/cvs.pm index 4735c0138..788f51167 100644 --- a/IkiWiki/Plugin/cvs.pm +++ b/IkiWiki/Plugin/cvs.pm @@ -1,13 +1,49 @@ -#!/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 URI::Escape q{uri_escape_utf8}; +use File::chdir; + + +# GENERAL PLUGIN API CALLS + sub import { hook(type => "checkconfig", id => "cvs", call => \&checkconfig); hook(type => "getsetup", id => "cvs", call => \&getsetup); + hook(type => "genwrapper", id => "cvs", call => \&genwrapper); + hook(type => "rcs", id => "rcs_update", call => \&rcs_update); hook(type => "rcs", id => "rcs_prepedit", call => \&rcs_prepedit); hook(type => "rcs", id => "rcs_commit", call => \&rcs_commit); @@ -18,6 +54,7 @@ sub import { hook(type => "rcs", id => "rcs_recentchanges", call => \&rcs_recentchanges); hook(type => "rcs", id => "rcs_diff", call => \&rcs_diff); hook(type => "rcs", id => "rcs_getctime", call => \&rcs_getctime); + hook(type => "rcs", id => "rcs_getmtime", call => \&rcs_getmtime); } sub checkconfig () { @@ -43,6 +80,7 @@ sub getsetup () { plugin => { safe => 0, # rcs plugin rebuild => undef, + section => "rcs", }, cvsrepo => { type => "string", @@ -61,7 +99,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, }, @@ -88,63 +126,23 @@ sub getsetup () { }, } -sub cvs_info ($$) { - my $field=shift; - my $file=shift; - - chdir $config{srcdir} || error("Cannot chdir to $config{srcdir}: $!"); - - my $info=`cvs status $file`; - my ($ret)=$info=~/^\s*$field:\s*(\S+)/m; - return $ret; -} - -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}: $!"); - - 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); +sub genwrapper () { + return < $cmd, verbose => 0); - if ((grep /'$test_message'/, @$stdout_buf) > 0) { - return IkiWiki::possibly_foolish_untaint($message); - } else { - return shell_quote(IkiWiki::possibly_foolish_untaint($message)); - } -} -sub cvs_is_controlling { - my $dir=shift; - $dir=$config{srcdir} unless defined($dir); - return (-d "$dir/CVS") ? 1 : 0; -} +# VCS PLUGIN API CALLS sub rcs_update () { - return unless cvs_is_controlling; - cvs_runcvs(['update', '-dP']); + return unless cvs_is_controlling(); + cvs_runcvs('update', '-dP'); } sub rcs_prepedit ($) { @@ -154,7 +152,7 @@ sub rcs_prepedit ($) { # The file is relative to the srcdir. my $file=shift; - return unless cvs_is_controlling; + return unless cvs_is_controlling(); # For cvs, return the revision of the file when # editing begins. @@ -162,39 +160,29 @@ sub rcs_prepedit ($) { return defined $rev ? $rev : ""; } -sub rcs_commit ($$$;$$) { +sub rcs_commit (@) { # Tries to commit the page; returns undef on _success_ and # a version of the page with the rcs's conflict markers on failure. # The file is relative to the srcdir. - my $file=shift; - my $message=shift; - my $rcstoken=shift; - my $user=shift; - my $ipaddr=shift; + my %params=@_; - return unless cvs_is_controlling; - - if (defined $user) { - $message="web commit by $user".(length $message ? ": $message" : ""); - } - elsif (defined $ipaddr) { - $message="web commit from $ipaddr".(length $message ? ": $message" : ""); - } + return unless cvs_is_controlling(); # Check to see if the page has been changed by someone # else since rcs_prepedit was called. - my ($oldrev)=$rcstoken=~/^([0-9]+)$/; # untaint - my $rev=cvs_info("Repository revision", "$config{srcdir}/$file"); + my ($oldrev)=$params{token}=~/^([0-9]+)$/; # untaint + my $rev=cvs_info("Repository revision", "$config{srcdir}/$params{file}"); if (defined $rev && defined $oldrev && $rev != $oldrev) { # Merge their changes into the file that we've # changed. - cvs_runcvs(['update', $file]) || + cvs_runcvs('update', $params{file}) || warn("cvs merge from $oldrev to $rev failed\n"); } - if (! cvs_runcvs(['commit', '-m', cvs_shquote_commit $message])) { - my $conflict=readfile("$config{srcdir}/$file"); - cvs_runcvs(['update', '-C', $file]) || + if (! cvs_runcvs('commit', '-m', + IkiWiki::possibly_foolish_untaint(commitmessage(%params)))) { + my $conflict=readfile("$config{srcdir}/$params{file}"); + cvs_runcvs('update', '-C', $params{file}) || warn("cvs revert failed\n"); return $conflict; } @@ -202,19 +190,13 @@ sub rcs_commit ($$$;$$) { return undef # success } -sub rcs_commit_staged ($$$) { +sub rcs_commit_staged (@) { # Commits all staged changes. Changes can be staged using rcs_add, # rcs_remove, and rcs_rename. - my ($message, $user, $ipaddr)=@_; + my %params=@_; - if (defined $user) { - $message="web commit by $user".(length $message ? ": $message" : ""); - } - elsif (defined $ipaddr) { - $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(commitmessage(%params)))) { warn "cvs staged commit failed\n"; return 1; # failure } @@ -227,9 +209,6 @@ 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); @@ -238,17 +217,12 @@ sub rcs_add ($) { while ($file = pop @files_to_add) { 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 { + cvs_runcvs('add', cvs_keyword_subst_args($file)) || + warn("cvs add $file failed\n"); + } + else { # directory - cvs_runcvs(['add', $file]) || + cvs_runcvs('add', $file) || warn("cvs add $file failed\n"); } } @@ -258,9 +232,9 @@ sub rcs_remove ($) { # filename is relative to the root of the srcdir my $file=shift; - return unless cvs_is_controlling; + return unless cvs_is_controlling(); - cvs_runcvs(['rm', '-f', $file]) || + cvs_runcvs('rm', '-f', $file) || warn("cvs rm $file failed\n"); } @@ -268,9 +242,9 @@ sub rcs_rename ($$) { # filenames relative to the root of the srcdir my ($src, $dest)=@_; - return unless cvs_is_controlling; + 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"); @@ -280,16 +254,16 @@ sub rcs_rename ($$) { rcs_remove($src); } -sub rcs_recentchanges($) { +sub rcs_recentchanges ($) { my $num = shift; my @ret; - return unless cvs_is_controlling; + return unless cvs_is_controlling(); 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. @@ -299,11 +273,12 @@ sub rcs_recentchanges($) { eval q{use File::ReadBackwards}; error($@) if $@; - my (undef, $tmpfile) = tempfile(OPEN=>0); + 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) { + } + elsif (($? >> 8) != 0) { error "cvsps exited " . ($? >> 8) . ": $!\n"; } @@ -341,7 +316,8 @@ sub rcs_recentchanges($) { $oldrev =~ s/INITIAL/0/; $newrev =~ s/\(DEAD\)//; my $diffurl = defined $config{diffurl} ? $config{diffurl} : ""; - $diffurl=~s/\[\[file\]\]/$page/g; + my $epage = uri_escape_utf8($page); + $diffurl=~s/\[\[file\]\]/$epage/g; $diffurl=~s/\[\[r1\]\]/$oldrev/g; $diffurl=~s/\[\[r2\]\]/$newrev/g; unshift @pages, { @@ -360,7 +336,8 @@ sub rcs_recentchanges($) { $message[0]->{line}=~/$config{web_commit_regexp}/) { $user=defined $2 ? "$2" : "$3"; $message[0]->{line}=$4; - } else { + } + else { $committype="cvs"; } @@ -370,21 +347,24 @@ sub rcs_recentchanges($) { $line = ; if ($line =~ /^Author: (.*)$/) { $user = $1 unless defined $user && length $user; - } else { + } + else { error "expected Author, got $line"; } $line = ; if ($line =~ /^Date: (.*)$/) { $when = str2time($1, 'UTC'); - } else { + } + else { error "expected Date, got $line"; } $line = ; if ($line =~ /^PatchSet (.*)$/) { $rev = $1; - } else { + } + else { error "expected PatchSet, got $line"; } @@ -406,10 +386,11 @@ sub rcs_recentchanges($) { return @ret; } -sub rcs_diff ($) { +sub rcs_diff ($;$) { my $rev=IkiWiki::possibly_foolish_untaint(int(shift)); + my $maxlines=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`; @@ -422,7 +403,8 @@ sub rcs_diff ($) { if (wantarray) { return @cvsps; - } else { + } + else { return join("", @cvsps); } } @@ -430,6 +412,8 @@ sub rcs_diff ($) { sub rcs_getctime ($) { my $file=shift; + local $CWD = $config{srcdir}; + my $cvs_log_infoline=qr/^date: (.+);\s+author/; open CVSLOG, "cvs -Q log -r1.1 '$file' |" @@ -455,4 +439,78 @@ sub rcs_getctime ($) { return $date; } +sub rcs_getmtime ($) { + error "rcs_getmtime is not implemented for cvs\n"; # TODO +} + + +# INTERNAL SUPPORT ROUTINES + +sub commitmessage (@) { + my %params=@_; + + if (defined $params{session}) { + if (defined $params{session}->param("name")) { + return "web commit by ". + $params{session}->param("name"). + (length $params{message} ? ": $params{message}" : ""); + } + elsif (defined $params{session}->remote_addr()) { + return "web commit from ". + $params{session}->remote_addr(). + (length $params{message} ? ": $params{message}" : ""); + } + } + return $params{message}; +} + +sub cvs_info ($$) { + my $field=shift; + my $file=shift; + + local $CWD = $config{srcdir}; + + my $info=`cvs status $file`; + my ($ret)=$info=~/^\s*$field:\s*(\S+)/m; + return $ret; +} + +sub cvs_is_controlling { + my $dir=shift; + $dir=$config{srcdir} unless defined($dir); + return (-d "$dir/CVS") ? 1 : 0; +} + +sub cvs_keyword_subst_args ($) { + my $file = shift; + + local $CWD = $config{srcdir}; + + eval q{use File::MimeInfo}; + error($@) if $@; + my $filemime = File::MimeInfo::default($file); + # if (-T $file) { + + if (defined($filemime) && $filemime eq 'text/plain') { + return ($file); + } + else { + return ('-kb', $file); + } +} + +sub cvs_runcvs(@) { + my @cmd = @_; + unshift @cmd, 'cvs', '-Q'; + + local $CWD = $config{srcdir}; + + open(my $savedout, ">&STDOUT"); + open(STDOUT, ">", "/dev/null"); + my $ret = system(@cmd); + open(STDOUT, ">&", $savedout); + + return ($ret == 0) ? 1 : 0; +} + 1