From: Joey Hess <joey@kodama.kitenet.net>
Date: Sun, 27 Jul 2008 02:27:58 +0000 (-0400)
Subject: finish with rcs plugin conversion
X-Git-Tag: 2.60~173
X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/commitdiff_plain/6154dd03cb9f46f58c04f91c12ac9e42c696dbbb?hp=14cd75746a2c73b50548b6fdb3583d536b6ef9bd

finish with rcs plugin conversion
---

diff --git a/IkiWiki.pm b/IkiWiki.pm
index 4ad19a499..0746ef24b 100644
--- a/IkiWiki.pm
+++ b/IkiWiki.pm
@@ -414,13 +414,10 @@ sub checkconfig () { #{{{
 		unless exists $config{wikistatedir};
 	
 	if ($config{rcs}) {
-		eval qq{use IkiWiki::Rcs::$config{rcs}};
-		if ($@) {
-			error("Failed to load RCS module IkiWiki::Rcs::$config{rcs}: $@");
-		}
+		loadplugin($config{rcs});
 	}
 	else {
-		require IkiWiki::Rcs::Stub;
+		loadplugin("norcs");
 	}
 
 	if (defined $config{umask}) {
@@ -1428,6 +1425,46 @@ sub run_hooks ($$) { # {{{
 	return 1;
 } #}}}
 
+sub rcs_update () { #{{{
+	$hooks{rcs}{rcs_update}{call}->(@_);
+} #}}}
+
+sub rcs_prepedit ($) { #{{{
+	$hooks{rcs}{rcs_prepedit}{call}->(@_);
+} #}}}
+
+sub rcs_commit ($$$;$$) { #{{{
+	$hooks{rcs}{rcs_commit}{call}->(@_);
+} #}}}
+
+sub rcs_commit_staged ($$$) { #{{{
+	$hooks{rcs}{rcs_commit_staged}{call}->(@_);
+} #}}}
+
+sub rcs_add ($) { #{{{
+	$hooks{rcs}{rcs_add}{call}->(@_);
+} #}}}
+
+sub rcs_remove ($) { #{{{
+	$hooks{rcs}{rcs_remove}{call}->(@_);
+} #}}}
+
+sub rcs_rename ($$) { #{{{
+	$hooks{rcs}{rcs_rename}{call}->(@_);
+} #}}}
+
+sub rcs_recentchanges ($) { #{{{
+	$hooks{rcs}{rcs_recentchanges}{call}->(@_);
+} #}}}
+
+sub rcs_diff ($) { #{{{
+	$hooks{rcs}{rcs_diff}{call}->(@_);
+} #}}}
+
+sub rcs_getctime ($) { #{{{
+	$hooks{rcs}{rcs_getctime}{call}->(@_);
+} #}}}
+
 sub globlist_to_pagespec ($) { #{{{
 	my @globlist=split(' ', shift);
 
diff --git a/IkiWiki/Plugin/bzr.pm b/IkiWiki/Plugin/bzr.pm
index 5df522f6e..39227cbae 100644
--- a/IkiWiki/Plugin/bzr.pm
+++ b/IkiWiki/Plugin/bzr.pm
@@ -1,6 +1,5 @@
 #!/usr/bin/perl
-
-package IkiWiki;
+package IkiWiki::Plugin::bzr;
 
 use warnings;
 use strict;
@@ -8,19 +7,34 @@ use IkiWiki;
 use Encode;
 use open qw{:utf8 :std};
 
-hook(type => "checkconfig", id => "bzr", call => sub { #{{{
+sub import { #{{{
+	hook(type => "checkconfig", id => "bzr", call => \&checkconfig);
+	hook(type => "getsetup", id => "bzr", call => \&getsetup);
+	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);
+	hook(type => "rcs", id => "rcs_commit_staged", call => \&rcs_commit_staged);
+	hook(type => "rcs", id => "rcs_add", call => \&rcs_add);
+	hook(type => "rcs", id => "rcs_remove", call => \&rcs_remove);
+	hook(type => "rcs", id => "rcs_rename", call => \&rcs_rename);
+	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);
+} #}}}
+
+sub checkconfig () { #{{{
 	if (! defined $config{diffurl}) {
 		$config{diffurl}="";
 	}
-	if (length $config{bzr_wrapper}) {
+	if (defined $config{bzr_wrapper} && length $config{bzr_wrapper}) {
 		push @{$config{wrappers}}, {
 			wrapper => $config{bzr_wrapper},
 			wrappermode => (defined $config{bzr_wrappermode} ? $config{bzr_wrappermode} : "06755"),
 		};
 	}
-}); #}}}
+} #}}}
 
-hook(type => "getsetup", id => "bzr", call => sub { #{{{
+sub getsetup () { #{{{
 	return
 		bzr_wrapper => {
 			type => "string",
@@ -50,7 +64,7 @@ hook(type => "getsetup", id => "bzr", call => sub { #{{{
 			safe => 1,
 			rebuild => 1,
 		},
-}); #}}}
+} #}}}
 
 sub bzr_log ($) { #{{{
 	my $out = shift;
@@ -101,10 +115,10 @@ sub bzr_author ($$) { #{{{
 	my ($user, $ipaddr) = @_;
 
 	if (defined $user) {
-		return possibly_foolish_untaint($user);
+		return IkiWiki::possibly_foolish_untaint($user);
 	}
 	elsif (defined $ipaddr) {
-		return "Anonymous from ".possibly_foolish_untaint($ipaddr);
+		return "Anonymous from ".IkiWiki::possibly_foolish_untaint($ipaddr);
 	}
 	else {
 		return "Anonymous";
@@ -116,7 +130,7 @@ sub rcs_commit ($$$;$$) { #{{{
 
 	$user = bzr_author($user, $ipaddr);
 
-	$message = possibly_foolish_untaint($message);
+	$message = IkiWiki::possibly_foolish_untaint($message);
 	if (! length $message) {
 		$message = "no message given";
 	}
@@ -137,7 +151,7 @@ sub rcs_commit_staged ($$$) {
 
 	$user = bzr_author($user, $ipaddr);
 
-	$message = possibly_foolish_untaint($message);
+	$message = IkiWiki::possibly_foolish_untaint($message);
 	if (! length $message) {
 		$message = "no message given";
 	}
@@ -172,7 +186,7 @@ sub rcs_remove ($) { # {{{
 sub rcs_rename ($$) { # {{{
 	my ($src, $dest) = @_;
 
-	my $parent = dirname($dest);
+	my $parent = IkiWiki::dirname($dest);
 	if (system("bzr", "add", "--quiet", "$config{srcdir}/$parent") != 0) {
 		warn("bzr add $parent failed\n");
 	}
diff --git a/IkiWiki/Plugin/git.pm b/IkiWiki/Plugin/git.pm
index 6c9aca650..b20793d86 100644
--- a/IkiWiki/Plugin/git.pm
+++ b/IkiWiki/Plugin/git.pm
@@ -1,6 +1,5 @@
 #!/usr/bin/perl
-
-package IkiWiki;
+package IkiWiki::Plugin::git;
 
 use warnings;
 use strict;
@@ -11,7 +10,22 @@ use open qw{:utf8 :std};
 my $sha1_pattern     = qr/[0-9a-fA-F]{40}/; # pattern to validate Git sha1sums
 my $dummy_commit_msg = 'dummy commit';      # message to skip in recent changes
 
-hook(type => "checkconfig", id => "git", call => sub { #{{{
+sub import { #{{{
+	hook(type => "checkconfig", id => "git", call => \&checkconfig);
+	hook(type => "getsetup", id => "git", call => \&getsetup);
+	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);
+	hook(type => "rcs", id => "rcs_commit_staged", call => \&rcs_commit_staged);
+	hook(type => "rcs", id => "rcs_add", call => \&rcs_add);
+	hook(type => "rcs", id => "rcs_remove", call => \&rcs_remove);
+	hook(type => "rcs", id => "rcs_rename", call => \&rcs_rename);
+	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);
+} #}}}
+
+sub checkconfig () { #{{{
 	if (! defined $config{diffurl}) {
 		$config{diffurl}="";
 	}
@@ -21,15 +35,15 @@ hook(type => "checkconfig", id => "git", call => sub { #{{{
 	if (! defined $config{gitmaster_branch}) {
 		$config{gitmaster_branch}="master";
 	}
-	if (length $config{git_wrapper}) {
+	if (defined $config{git_wrapper} && length $config{git_wrapper}) {
 		push @{$config{wrappers}}, {
 			wrapper => $config{git_wrapper},
 			wrappermode => (defined $config{git_wrappermode} ? $config{git_wrappermode} : "06755"),
 		};
 	}
-}); #}}}
+} #}}}
 
-hook(type => "getsetup", id => "git", call => sub { #{{{
+sub getsetup () { #{{{
 	return
 		git_wrapper => {
 			type => "string",
@@ -73,9 +87,9 @@ hook(type => "getsetup", id => "git", call => sub { #{{{
 			safe => 0, # paranoia
 			rebuild => 0,
 		},
-}); #}}}
+} #}}}
 
-sub _safe_git (&@) { #{{{
+sub safe_git (&@) { #{{{
 	# Start a child process safely without resorting /bin/sh.
 	# Return command output or success state (in scalar context).
 
@@ -107,12 +121,12 @@ sub _safe_git (&@) { #{{{
 	return wantarray ? @lines : ($? == 0);
 }
 # Convenient wrappers.
-sub run_or_die ($@) { _safe_git(\&error, @_) }
-sub run_or_cry ($@) { _safe_git(sub { warn @_ },  @_) }
-sub run_or_non ($@) { _safe_git(undef,            @_) }
+sub run_or_die ($@) { safe_git(\&error, @_) }
+sub run_or_cry ($@) { safe_git(sub { warn @_ },  @_) }
+sub run_or_non ($@) { safe_git(undef,            @_) }
 #}}}
 
-sub _merge_past ($$$) { #{{{
+sub merge_past ($$$) { #{{{
 	# Unlike with Subversion, Git cannot make a 'svn merge -rN:M file'.
 	# Git merge commands work with the committed changes, except in the
 	# implicit case of '-m' of git checkout(1).  So we should invent a
@@ -206,7 +220,7 @@ sub _merge_past ($$$) { #{{{
 	return $conflict;
 } #}}}
 
-sub _parse_diff_tree ($@) { #{{{
+sub parse_diff_tree ($@) { #{{{
 	# Parse the raw diff tree chunk and return the info hash.
 	# See git-diff-tree(1) for the syntax.
 
@@ -326,7 +340,7 @@ sub git_commit_info ($;$) { #{{{
 	my ($prefix) = run_or_die('git', 'rev-parse', '--show-prefix');
 
 	my @ci;
-	while (my $parsed = _parse_diff_tree(($prefix or ""), \@raw_lines)) {
+	while (my $parsed = parse_diff_tree(($prefix or ""), \@raw_lines)) {
 		push @ci, $parsed;
 	}
 
@@ -379,7 +393,7 @@ sub rcs_commit ($$$;$$) { #{{{
 	my ($prev) = $rcstoken =~ /^($sha1_pattern)$/; # untaint
 
 	if (defined $cur && defined $prev && $cur ne $prev) {
-		my $conflict = _merge_past($prev, $file, $dummy_commit_msg);
+		my $conflict = merge_past($prev, $file, $dummy_commit_msg);
 		return $conflict if defined $conflict;
 	}
 
@@ -402,7 +416,7 @@ sub rcs_commit_staged ($$$) {
 
 	# git commit returns non-zero if file has not been really changed.
 	# so we should ignore its exit status (hence run_or_non).
-	$message = possibly_foolish_untaint($message);
+	$message = IkiWiki::possibly_foolish_untaint($message);
 	if (run_or_non('git', 'commit', '--cleanup=verbatim',
 	               '-q', '-m', $message)) {
 		if (length $config{gitorigin_branch}) {
diff --git a/IkiWiki/Plugin/mercurial.pm b/IkiWiki/Plugin/mercurial.pm
index 3a98e09d8..738be8c32 100644
--- a/IkiWiki/Plugin/mercurial.pm
+++ b/IkiWiki/Plugin/mercurial.pm
@@ -1,6 +1,5 @@
 #!/usr/bin/perl
-
-package IkiWiki;
+package IkiWiki::Plugin::mercurial;
 
 use warnings;
 use strict;
@@ -8,19 +7,34 @@ use IkiWiki;
 use Encode;
 use open qw{:utf8 :std};
 
-hook(type => "checkconfig", id => "mercurial", call => sub { #{{{
+sub import { #{{{
+	hook(type => "checkconfig", id => "mercurial", call => \&checkconfig);
+	hook(type => "getsetup", id => "mercurial", call => \&getsetup);
+	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);
+	hook(type => "rcs", id => "rcs_commit_staged", call => \&rcs_commit_staged);
+	hook(type => "rcs", id => "rcs_add", call => \&rcs_add);
+	hook(type => "rcs", id => "rcs_remove", call => \&rcs_remove);
+	hook(type => "rcs", id => "rcs_rename", call => \&rcs_rename);
+	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);
+} #}}}
+
+sub checkconfig () { #{{{
 	if (! defined $config{diffurl}) {
 		$config{diffurl}="";
 	}
-	if (length $config{mercurial_wrapper}) {
+	if (exists $config{mercurial_wrapper} && length $config{mercurial_wrapper}) {
 		push @{$config{wrappers}}, {
 			wrapper => $config{mercurial_wrapper},
 			wrappermode => (defined $config{mercurial_wrappermode} ? $config{mercurial_wrappermode} : "06755"),
 		};
 	}
-}); #}}}
+} #}}}
 
-hook(type => "getsetup", id => "mercurial", call => sub { #{{{
+sub getsetup () { #{{{
 	return
 		mercurial_wrapper => {
 			type => "string",
@@ -50,7 +64,7 @@ hook(type => "getsetup", id => "mercurial", call => sub { #{{{
 			safe => 1,
 			rebuild => 1,
 		},
-}); #}}}
+} #}}}
 
 sub mercurial_log ($) { #{{{
 	my $out = shift;
@@ -113,16 +127,16 @@ sub rcs_commit ($$$;$$) { #{{{
 	my ($file, $message, $rcstoken, $user, $ipaddr) = @_;
 
 	if (defined $user) {
-		$user = possibly_foolish_untaint($user);
+		$user = IkiWiki::possibly_foolish_untaint($user);
 	}
 	elsif (defined $ipaddr) {
-		$user = "Anonymous from ".possibly_foolish_untaint($ipaddr);
+		$user = "Anonymous from ".IkiWiki::possibly_foolish_untaint($ipaddr);
 	}
 	else {
 		$user = "Anonymous";
 	}
 
-	$message = possibly_foolish_untaint($message);
+	$message = IkiWiki::possibly_foolish_untaint($message);
 	if (! length $message) {
 		$message = "no message given";
 	}
diff --git a/IkiWiki/Plugin/monotone.pm b/IkiWiki/Plugin/monotone.pm
index d7e8f296a..4b9be316a 100644
--- a/IkiWiki/Plugin/monotone.pm
+++ b/IkiWiki/Plugin/monotone.pm
@@ -1,6 +1,5 @@
 #!/usr/bin/perl
-
-package IkiWiki;
+package IkiWiki::Plugin::monotone;
 
 use warnings;
 use strict;
@@ -11,7 +10,22 @@ use Date::Format qw(time2str);
 
 my $sha1_pattern = qr/[0-9a-fA-F]{40}/; # pattern to validate sha1sums
 
-hook(type => "checkconfig", id => "monotone", call => sub { #{{{
+sub import { #{{{
+	hook(type => "checkconfig", id => "monotone", call => \&checkconfig);
+	hook(type => "getsetup", id => "monotone", call => \&getsetup);
+	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);
+	hook(type => "rcs", id => "rcs_commit_staged", call => \&rcs_commit_staged);
+	hook(type => "rcs", id => "rcs_add", call => \&rcs_add);
+	hook(type => "rcs", id => "rcs_remove", call => \&rcs_remove);
+	hook(type => "rcs", id => "rcs_rename", call => \&rcs_rename);
+	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);
+} #}}}
+
+sub checkconfig () { #{{{
 	if (!defined($config{mtnrootdir})) {
 		$config{mtnrootdir} = $config{srcdir};
 	}
@@ -47,9 +61,9 @@ hook(type => "checkconfig", id => "monotone", call => sub { #{{{
 			wrappermode => (defined $config{mtn_wrappermode} ? $config{mtn_wrappermode} : "06755"),
 		};
 	}
-}); #}}}
+} #}}}
 
-hook(type => "getsetup", id => "monotone", call => sub { #{{{
+sub getsetup () { #{{{
 	return
 		mtn_wrapper => {
 			type => "string",
@@ -99,7 +113,7 @@ hook(type => "getsetup", id => "monotone", call => sub { #{{{
 			safe => 0, # path
 			rebuild => 0,
 		},
-}); #}}}
+} #}}}
 
 sub get_rev () { #{{{
 	my $sha1 = `mtn --root=$config{mtnrootdir} automate get_base_revision_id`;
@@ -156,7 +170,7 @@ sub mtn_merge ($$$$) { #{{{
 	return $mergeRev;
 } #}}}
 
-sub commit_file_to_new_rev($$$$$$$$) { #{{{
+sub commit_file_to_new_rev ($$$$$$$$) { #{{{
 	my $automator=shift;
 	my $wsfilename=shift;
 	my $oldFileID=shift;
@@ -398,7 +412,7 @@ sub rcs_commit ($$$;$$) { #{{{
 
 	if (system("mtn", "--root=$config{mtnrootdir}", "commit", "--quiet",
 	           "--author", $author, "--key", $config{mtnkey}, "-m",
-		   possibly_foolish_untaint($message), $file) != 0) {
+		   IkiWiki::possibly_foolish_untaint($message), $file) != 0) {
 		debug("Traditional commit failed! Returning data as conflict.");
 		my $conflict=readfile("$config{srcdir}/$file");
 		if (system("mtn", "--root=$config{mtnrootdir}", "revert",
@@ -443,7 +457,7 @@ sub rcs_commit_staged ($$$) {
 
 	if (system("mtn", "--root=$config{mtnrootdir}", "commit", "--quiet",
 	           "--author", $author, "--key", $config{mtnkey}, "-m",
-		   possibly_foolish_untaint($message)) != 0) {
+		   IkiWiki::possibly_foolish_untaint($message)) != 0) {
 		error("Monotone commit failed");
 	}
 }
diff --git a/IkiWiki/Plugin/norcs.pm b/IkiWiki/Plugin/norcs.pm
index 04ba5f028..72c66569c 100644
--- a/IkiWiki/Plugin/norcs.pm
+++ b/IkiWiki/Plugin/norcs.pm
@@ -1,99 +1,58 @@
 #!/usr/bin/perl
 # Stubs for no revision control.
-
-package IkiWiki;
+package IkiWiki::Plugin::norcs;
 
 use warnings;
 use strict;
 use IkiWiki;
 
-sub rcs_update () {
-	# Update working directory to current version.
-	# (May be more complex for distributed RCS.)
-}
-
-sub rcs_prepedit ($) {
-	# Prepares to edit a file under revision control. Returns a token
-	# that must be passed into rcs_commit when the file is ready
-	# for committing.
-	# The file is relative to the srcdir.
+sub import { #{{{
+	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);
+	hook(type => "rcs", id => "rcs_commit_staged", call => \&rcs_commit_staged);
+	hook(type => "rcs", id => "rcs_add", call => \&rcs_add);
+	hook(type => "rcs", id => "rcs_remove", call => \&rcs_remove);
+	hook(type => "rcs", id => "rcs_rename", call => \&rcs_rename);
+	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);
+} #}}}
+
+sub rcs_update () { #{{{
+} #}}}
+
+sub rcs_prepedit ($) { #{{{
 	return ""
-}
+} #}}}
 
-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.
+sub rcs_commit ($$$;$$) { #{{{
 	my ($file, $message, $rcstoken, $user, $ipaddr) = @_;
 	return undef # success
-}
+} #}}}
 
-sub rcs_commit_staged ($$$) {
-	# Commits all staged changes. Changes can be staged using rcs_add,
-	# rcs_remove, and rcs_rename.
+sub rcs_commit_staged ($$$) { #{{{
 	my ($message, $user, $ipaddr)=@_;
 	return undef # success
-}
+} #}}}
 
-sub rcs_add ($) {
-	# Add a file. The filename is relative to the root of the srcdir.
-	# Note that this should not check the new file in, it should only
-	# prepare for it to be checked in when rcs_commit is called.
-	# Note that the file may be in a new subdir that is not yet added
-	# to version control; the subdir can be added if so.
-}
+sub rcs_add ($) { #{{{
+} #}}}
 
-sub rcs_remove ($) {
-	# Remove a file. The filename is relative to the root of the srcdir.
-	# Note that this should not check the removal in, it should only
-	# prepare for it to be checked in when rcs_commit is called.
-	# Note that the new file may be in a new subdir that is not yet added
-	# to version control; the subdir can be added if so.
-}
+sub rcs_remove ($) { #{{{
+} #}}}
 
-sub rcs_rename ($$) {
-	# Rename a file. The filenames are relative to the root of the srcdir.
-	# Note that this should not commit the rename, it should only
-	# prepare it for when rcs_commit is called.
-	# The new filename may be in a new subdir, that is not yet added to
-	# version control. If so, the subdir will exist already, and should
-	# be added to revision control.
-}
+sub rcs_rename ($$) { #{{{
+} #}}}
 
-sub rcs_recentchanges ($) {
-	# Examine the RCS history and generate a list of recent changes.
-	# The data structure returned for each change is:
-	# {
-	# 	rev => # the RCSs id for this commit
-	# 	user => # name of user who made the change,
-	# 	committype => # either "web" or the name of the rcs,
-	# 	when => # time when the change was made,
-	# 	message => [
-	# 		{ line => "commit message line" },
-	# 		{ line => "commit message line" },
-	# 		# etc,
-	# 	],
-	# 	pages => [
-	# 		{
-	# 			page => # name of page changed,
-	#			diffurl => # optional url to a diff showing 
-	#			           # the changes,
-	# 		},
-	# 		# repeat for each page changed in this commit,
-	# 	],
-	# }
-}
+sub rcs_recentchanges ($) { #{{{
+} #}}}
 
-sub rcs_diff ($) {
-	# Optional, used to get diffs for recentchanges.
-	# The parameter is the rev from rcs_recentchanges.
-	# Should return a list of lines of the diff (including \n) in list
-	# context, and the whole diff in scalar context.
-}
+sub rcs_diff ($) { #{{{
+} #}}}
 
-sub rcs_getctime ($) {
-	# Optional, used to get the page creation time from the RCS.
+sub rcs_getctime ($) { #{{{
 	error gettext("getctime not implemented");
-}
+} #}}}
 
 1
diff --git a/IkiWiki/Plugin/svn.pm b/IkiWiki/Plugin/svn.pm
index 0e7df3659..05312a1ed 100644
--- a/IkiWiki/Plugin/svn.pm
+++ b/IkiWiki/Plugin/svn.pm
@@ -1,13 +1,27 @@
 #!/usr/bin/perl
-
-package IkiWiki;
+package IkiWiki::Plugin::svn;
 
 use warnings;
 use strict;
 use IkiWiki;
 use POSIX qw(setlocale LC_CTYPE);
 
-hook(type => "checkconfig", id => "svn", call => sub { #{{{
+sub import { #{{{
+	hook(type => "checkconfig", id => "svn", call => \&checkconfig);
+	hook(type => "getsetup", id => "svn", call => \&getsetup);
+	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);
+	hook(type => "rcs", id => "rcs_commit_staged", call => \&rcs_commit_staged);
+	hook(type => "rcs", id => "rcs_add", call => \&rcs_add);
+	hook(type => "rcs", id => "rcs_remove", call => \&rcs_remove);
+	hook(type => "rcs", id => "rcs_rename", call => \&rcs_rename);
+	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);
+} #}}}
+
+sub checkconfig () { #{{{
 	if (! defined $config{diffurl}) {
 		$config{diffurl}="";
 	}
@@ -20,15 +34,15 @@ hook(type => "checkconfig", id => "svn", call => sub { #{{{
 		$config{svnpath}=~s/\/$//;
 		$config{svnpath}=~s/^\///;
 	}
-	if (length $config{svn_wrapper}) {
+	if (defined $config{svn_wrapper} && length $config{svn_wrapper}) {
 		push @{$config{wrappers}}, {
 			wrapper => $config{svn_wrapper},
 			wrappermode => (defined $config{svn_wrappermode} ? $config{svn_wrappermode} : "04755"),
 		};
 	}
-}); #}}}
+} #}}}
 
-hook(type => "getsetup", id => "svn", call => sub { #{{{
+sub getsetup () { #{{{
 	return
 		svnrepo => {
 			type => "string",
@@ -72,7 +86,7 @@ hook(type => "getsetup", id => "svn", call => sub { #{{{
 			safe => 1,
 			rebuild => 1,
 		},
-}); #}}}
+} #}}}
 
 # svn needs LC_CTYPE set to a UTF-8 locale, so try to find one. Any will do.
 sub find_lc_ctype() {
@@ -160,7 +174,7 @@ sub rcs_commit ($$$;$$) { #{{{
 
 		if (system("svn", "commit", "--quiet", 
 		           "--encoding", "UTF-8", "-m",
-		           possibly_foolish_untaint($message),
+		           IkiWiki::possibly_foolish_untaint($message),
 			   $config{srcdir}) != 0) {
 			my $conflict=readfile("$config{srcdir}/$file");
 			if (system("svn", "revert", "--quiet", "$config{srcdir}/$file") != 0) {
@@ -186,7 +200,7 @@ sub rcs_commit_staged ($$$) {
 	
 	if (system("svn", "commit", "--quiet",
 	           "--encoding", "UTF-8", "-m",
-	           possibly_foolish_untaint($message),
+	           IkiWiki::possibly_foolish_untaint($message),
 		   $config{srcdir}) != 0) {
 		warn("svn commit failed\n");
 		return 1; # failure	
@@ -199,10 +213,10 @@ sub rcs_add ($) { #{{{
 	my $file=shift;
 
 	if (-d "$config{srcdir}/.svn") {
-		my $parent=dirname($file);
+		my $parent=IkiWiki::dirname($file);
 		while (! -d "$config{srcdir}/$parent/.svn") {
 			$file=$parent;
-			$parent=dirname($file);
+			$parent=IkiWiki::dirname($file);
 		}
 		
 		if (system("svn", "add", "--quiet", "$config{srcdir}/$file") != 0) {
@@ -329,7 +343,7 @@ sub rcs_recentchanges ($) { #{{{
 } #}}}
 
 sub rcs_diff ($) { #{{{
-	my $rev=possibly_foolish_untaint(int(shift));
+	my $rev=IkiWiki::possibly_foolish_untaint(int(shift));
 	return `svnlook diff $config{svnrepo} -r$rev --no-diff-deleted`;
 } #}}}
 
diff --git a/IkiWiki/Plugin/tla.pm b/IkiWiki/Plugin/tla.pm
index e1389a346..b95c1a522 100644
--- a/IkiWiki/Plugin/tla.pm
+++ b/IkiWiki/Plugin/tla.pm
@@ -1,24 +1,38 @@
 #!/usr/bin/perl
-
-package IkiWiki;
+package IkiWiki::Plugin::tla;
 
 use warnings;
 use strict;
 use IkiWiki;
 
-hook(type => "checkconfig", id => "tla", call => sub { #{{{
+sub import { #{{{
+	hook(type => "checkconfig", id => "tla", call => \&checkconfig);
+	hook(type => "getsetup", id => "tla", call => \&getsetup);
+	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);
+	hook(type => "rcs", id => "rcs_commit_staged", call => \&rcs_commit_staged);
+	hook(type => "rcs", id => "rcs_add", call => \&rcs_add);
+	hook(type => "rcs", id => "rcs_remove", call => \&rcs_remove);
+	hook(type => "rcs", id => "rcs_rename", call => \&rcs_rename);
+	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);
+} #}}}
+
+sub checkconfig () { #{{{
 	if (! defined $config{diffurl}) {
 		$config{diffurl}="";
 	}
-	if (length $config{tla_wrapper}) {
+	if (defined $config{tla_wrapper} && length $config{tla_wrapper}) {
 		push @{$config{wrappers}}, {
 			wrapper => $config{tla_wrapper},
 			wrappermode => (defined $config{tla_wrappermode} ? $config{tla_wrappermode} : "06755"),
 		};
 	}
-}); #}}}
+} #}}}
 
-hook(type => "getsetup", id => "tla", call => sub { #{{{
+sub getsetup () { #{{{
 	return
 		tla_wrapper => {
 			type => "string",
@@ -48,9 +62,9 @@ hook(type => "getsetup", id => "tla", call => sub { #{{{
 			safe => 1,
 			rebuild => 1,
 		},
-}); #}}}
+} #}}}
 
-sub quiet_system (@) {
+sub quiet_system (@) { #{{{
 	# See Debian bug #385939.
 	open (SAVEOUT, ">&STDOUT");
 	close STDOUT;
@@ -60,7 +74,7 @@ sub quiet_system (@) {
 	open (STDOUT, ">&SAVEOUT");
 	close SAVEOUT;
 	return $ret;
-}
+} #}}}
 
 sub rcs_update () { #{{{
 	if (-d "$config{srcdir}/{arch}") {
@@ -110,7 +124,7 @@ sub rcs_commit ($$$;$$) { #{{{
 		}
 
 		if (quiet_system("tla", "commit",
-		           "-L".possibly_foolish_untaint($message),
+		           "-L".IkiWiki::possibly_foolish_untaint($message),
 			   '-d', $config{srcdir}) != 0) {
 			my $conflict=readfile("$config{srcdir}/$file");
 			if (system("tla", "undo", "-n", "--quiet", "-d", "$config{srcdir}") != 0) {
diff --git a/debian/changelog b/debian/changelog
index 205620508..78a18b615 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -3,9 +3,11 @@ ikiwiki (2.60) UNRELEASED; urgency=low
   * Add getsetup hook, all plugins that add fields to %config should use it.
   * ikiwiki --dumpsetup can generate a nice setup file snapshotting ikiwiki's
     current configuration.
+  * Large amounts of internal config data reorg.
   * The way wrappers are defined in the setup file has changed. Old setup
     files will continue to work, for now.
- 
+  * Version control backends promoted to first-class plugins.
+
  -- Joey Hess <joeyh@debian.org>  Mon, 21 Jul 2008 11:35:46 -0400
 
 ikiwiki (2.55) UNRELEASED; urgency=low
diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn
index 686f7e518..896b98827 100644
--- a/doc/plugins/write.mdwn
+++ b/doc/plugins/write.mdwn
@@ -654,15 +654,107 @@ PageSpecs glob patterns, but instead only by a special `internal()`
 
 ### RCS plugins
 
-ikiwiki's support for [[revision_control_systems|rcs]] also uses pluggable
-perl modules. These are in the `IkiWiki::RCS` namespace, for example
-`IkiWiki::RCS::svn`. 
+ikiwiki's support for [[revision_control_systems|rcs]] is also done via
+plugins. See [[RCS_details|rcs/details]] for some more info.
 
-Each RCS plugin must support all the `IkiWiki::rcs_*` functions.
-See IkiWiki::RCS::Stub for the full list of functions. It's ok if
-`rcs_getctime` does nothing except for throwing an error.
+RCS plugins must register a number of hooks. Each hook has type 'rcs', 
+and the 'id' field is set to the name of the hook. For example:
+	
+	hook(type => "rcs", id => "rcs_update", call => \&rcs_update);
+	hook(type => "rcs", id => "rcs_prepedit", call => \&rcs_prepedit);
 
-See [[RCS_details|rcs/details]] for some more info.
+#### `rcs_update()`
+
+Updates the working directory with any remote changes.
+
+#### `rcs_prepedit($)`
+
+Is passed a file to prepare to edit. It can generate and return an arbitrary
+token, that will be passed into `rcs_commit` when committing. For example,
+it might return the current revision ID of the file, and use that
+information later when merging changes.
+
+#### `rcs_commit($$$;$$)`
+
+Passed a file, message, token (from `rcs_prepedit`), user, and ip address.
+Should try to commit the file. Returns `undef` on *success* and a version
+of the page with the rcs's conflict markers on failure.
+
+#### `rcs_commit_staged($$$)`
+
+Passed a message, user, and ip address. Should commit all staged changes.
+Returns undef on success, and an error message on failure.
+
+Changes can be staged by calls to `rcs_add, `rcs_remove`, and
+`rcs_rename`.
+
+#### `rcs_add($)`
+
+Adds the passed file to the archive. The filename is relative to the root
+of the srcdir.
+
+Note that this should not check the new file in, it should only
+prepare for it to be checked in when rcs_commit (or `rcs_commit_staged`) is
+called. Note that the file may be in a new subdir that is not yet in
+to version control; the subdir can be added if so.
+
+#### `rcs_remove($)`
+
+Remove a file. The filename is relative to the root of the srcdir.
+
+Note that this should not check the removal in, it should only prepare for it
+to be checked in when `rcs_commit` (or `rcs_commit_staged`) is called. Note
+that the new file may be in a new subdir that is not yet inversion
+control; the subdir can be added if so.
+
+#### `rcs_rename($$)`
+
+Rename a file. The filenames are relative to the root of the srcdir.
+
+Note that this should not commit the rename, it should only
+prepare it for when `rcs_commit` (or `rcs_commit_staged`) is called.
+The new filename may be in a new subdir, that is not yet added to
+version control. If so, the subdir will exist already, and should
+be added to revision control.
+
+#### `rcs_recentchanges($)`
+
+Examine the RCS history and generate a list of recent changes.
+The parameter is how many changes to return.
+
+The data structure returned for each change is:
+
+	{
+		rev => # the RCSs id for this commit
+		user => # name of user who made the change,
+		committype => # either "web" or the name of the rcs,
+		when => # time when the change was made,
+		message => [
+			{ line => "commit message line 1" },
+			{ line => "commit message line 2" },
+			# etc,
+		],
+		pages => [
+			{
+				page => # name of page changed,
+				diffurl => # optional url to a diff of changes
+			},
+			# repeat for each page changed in this commit,
+		],
+	}
+
+#### `rcs_diff($)`
+
+The parameter is the rev from `rcs_recentchanges`.
+Should return a list of lines of the diff (including \n) in list
+context, and the whole diff in scalar context.
+
+#### `rcs_getctime($)`
+
+This is used to get the page creation time for a file from the RCS, by looking
+it up in the history.
+
+It's ok if this is not implemented, and throws an error.
 
 ### PageSpec plugins
 
diff --git a/po/ikiwiki.pot b/po/ikiwiki.pot
index b6e2dc68c..50c2fb87d 100644
--- a/po/ikiwiki.pot
+++ b/po/ikiwiki.pot
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-07-25 16:16-0400\n"
+"POT-Creation-Date: 2008-07-26 22:24-0400\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -50,7 +50,7 @@ msgid "%s is not an editable page"
 msgstr ""
 
 #: ../IkiWiki/CGI.pm:437 ../IkiWiki/Plugin/brokenlinks.pm:24
-#: ../IkiWiki/Plugin/inline.pm:261 ../IkiWiki/Plugin/opendiscussion.pm:17
+#: ../IkiWiki/Plugin/inline.pm:306 ../IkiWiki/Plugin/opendiscussion.pm:17
 #: ../IkiWiki/Plugin/orphans.pm:28 ../IkiWiki/Render.pm:78
 #: ../IkiWiki/Render.pm:148
 msgid "discussion"
@@ -71,122 +71,122 @@ msgstr ""
 msgid "You are banned."
 msgstr ""
 
-#: ../IkiWiki/CGI.pm:758 ../IkiWiki/CGI.pm:759 ../IkiWiki.pm:788
+#: ../IkiWiki/CGI.pm:758 ../IkiWiki/CGI.pm:759 ../IkiWiki.pm:1086
 msgid "Error"
 msgstr ""
 
-#: ../IkiWiki/Plugin/aggregate.pm:57
+#: ../IkiWiki/Plugin/aggregate.pm:76
 msgid "Aggregation triggered via web."
 msgstr ""
 
-#: ../IkiWiki/Plugin/aggregate.pm:66
+#: ../IkiWiki/Plugin/aggregate.pm:85
 msgid "Nothing to do right now, all feeds are up-to-date!"
 msgstr ""
 
-#: ../IkiWiki/Plugin/aggregate.pm:193
+#: ../IkiWiki/Plugin/aggregate.pm:212
 #, perl-format
 msgid "missing %s parameter"
 msgstr ""
 
-#: ../IkiWiki/Plugin/aggregate.pm:227
+#: ../IkiWiki/Plugin/aggregate.pm:246
 msgid "new feed"
 msgstr ""
 
-#: ../IkiWiki/Plugin/aggregate.pm:241
+#: ../IkiWiki/Plugin/aggregate.pm:260
 msgid "posts"
 msgstr ""
 
-#: ../IkiWiki/Plugin/aggregate.pm:243
+#: ../IkiWiki/Plugin/aggregate.pm:262
 msgid "new"
 msgstr ""
 
-#: ../IkiWiki/Plugin/aggregate.pm:406
+#: ../IkiWiki/Plugin/aggregate.pm:425
 #, perl-format
 msgid "expiring %s (%s days old)"
 msgstr ""
 
-#: ../IkiWiki/Plugin/aggregate.pm:413
+#: ../IkiWiki/Plugin/aggregate.pm:432
 #, perl-format
 msgid "expiring %s"
 msgstr ""
 
-#: ../IkiWiki/Plugin/aggregate.pm:440
+#: ../IkiWiki/Plugin/aggregate.pm:459
 #, perl-format
 msgid "processed ok at %s"
 msgstr ""
 
-#: ../IkiWiki/Plugin/aggregate.pm:444
+#: ../IkiWiki/Plugin/aggregate.pm:463
 #, perl-format
 msgid "checking feed %s ..."
 msgstr ""
 
-#: ../IkiWiki/Plugin/aggregate.pm:449
+#: ../IkiWiki/Plugin/aggregate.pm:468
 #, perl-format
 msgid "could not find feed at %s"
 msgstr ""
 
-#: ../IkiWiki/Plugin/aggregate.pm:464
+#: ../IkiWiki/Plugin/aggregate.pm:483
 msgid "feed not found"
 msgstr ""
 
-#: ../IkiWiki/Plugin/aggregate.pm:475
+#: ../IkiWiki/Plugin/aggregate.pm:494
 #, perl-format
 msgid "(invalid UTF-8 stripped from feed)"
 msgstr ""
 
-#: ../IkiWiki/Plugin/aggregate.pm:481
+#: ../IkiWiki/Plugin/aggregate.pm:500
 #, perl-format
 msgid "(feed entities escaped)"
 msgstr ""
 
-#: ../IkiWiki/Plugin/aggregate.pm:487
+#: ../IkiWiki/Plugin/aggregate.pm:506
 msgid "feed crashed XML::Feed!"
 msgstr ""
 
-#: ../IkiWiki/Plugin/aggregate.pm:561
+#: ../IkiWiki/Plugin/aggregate.pm:580
 #, perl-format
 msgid "creating new page %s"
 msgstr ""
 
-#: ../IkiWiki/Plugin/amazon_s3.pm:30
+#: ../IkiWiki/Plugin/amazon_s3.pm:31
 msgid "deleting bucket.."
 msgstr ""
 
-#: ../IkiWiki/Plugin/amazon_s3.pm:37 ../IkiWiki/Setup.pm:117
+#: ../IkiWiki/Plugin/amazon_s3.pm:38 ../ikiwiki.in:188
 msgid "done"
 msgstr ""
 
-#: ../IkiWiki/Plugin/amazon_s3.pm:46
+#: ../IkiWiki/Plugin/amazon_s3.pm:93
 #, perl-format
 msgid "Must specify %s"
 msgstr ""
 
-#: ../IkiWiki/Plugin/amazon_s3.pm:85
+#: ../IkiWiki/Plugin/amazon_s3.pm:132
 msgid "Failed to create bucket in S3: "
 msgstr ""
 
-#: ../IkiWiki/Plugin/amazon_s3.pm:170
+#: ../IkiWiki/Plugin/amazon_s3.pm:217
 msgid "Failed to save file to S3: "
 msgstr ""
 
-#: ../IkiWiki/Plugin/amazon_s3.pm:192
+#: ../IkiWiki/Plugin/amazon_s3.pm:239
 msgid "Failed to delete file from S3: "
 msgstr ""
 
-#: ../IkiWiki/Plugin/attachment.pm:22
+#: ../IkiWiki/Plugin/attachment.pm:34
 #, perl-format
 msgid "there is already a page named %s"
 msgstr ""
 
-#: ../IkiWiki/Plugin/attachment.pm:41
+#: ../IkiWiki/Plugin/attachment.pm:53
 msgid "prohibited by allowed_attachments"
 msgstr ""
 
-#: ../IkiWiki/Plugin/attachment.pm:144
+#: ../IkiWiki/Plugin/attachment.pm:156
 msgid "bad attachment filename"
 msgstr ""
 
-#: ../IkiWiki/Plugin/attachment.pm:186
+#: ../IkiWiki/Plugin/attachment.pm:198
 msgid "attachment upload"
 msgstr ""
 
@@ -262,33 +262,33 @@ msgstr ""
 msgid "failed to determine size of image %s"
 msgstr ""
 
-#: ../IkiWiki/Plugin/inline.pm:47
+#: ../IkiWiki/Plugin/inline.pm:89
 msgid "Must specify url to wiki with --url when using --rss or --atom"
 msgstr ""
 
-#: ../IkiWiki/Plugin/inline.pm:101
+#: ../IkiWiki/Plugin/inline.pm:146
 msgid "missing pages parameter"
 msgstr ""
 
-#: ../IkiWiki/Plugin/inline.pm:149
+#: ../IkiWiki/Plugin/inline.pm:194
 #, perl-format
 msgid "unknown sort type %s"
 msgstr ""
 
-#: ../IkiWiki/Plugin/inline.pm:220
+#: ../IkiWiki/Plugin/inline.pm:265
 msgid "Add a new post titled:"
 msgstr ""
 
-#: ../IkiWiki/Plugin/inline.pm:236
+#: ../IkiWiki/Plugin/inline.pm:281
 #, perl-format
 msgid "nonexistant template %s"
 msgstr ""
 
-#: ../IkiWiki/Plugin/inline.pm:269 ../IkiWiki/Render.pm:82
+#: ../IkiWiki/Plugin/inline.pm:314 ../IkiWiki/Render.pm:82
 msgid "Discussion"
 msgstr ""
 
-#: ../IkiWiki/Plugin/inline.pm:506
+#: ../IkiWiki/Plugin/inline.pm:551
 msgid "RPC::XML::Client not found, not pinging"
 msgstr ""
 
@@ -301,11 +301,11 @@ msgstr ""
 msgid "%s is locked by %s and cannot be edited"
 msgstr ""
 
-#: ../IkiWiki/Plugin/mdwn.pm:28
+#: ../IkiWiki/Plugin/mdwn.pm:40
 msgid "multimarkdown is enabled, but Text::MultiMarkdown is not installed"
 msgstr ""
 
-#: ../IkiWiki/Plugin/mdwn.pm:51
+#: ../IkiWiki/Plugin/mdwn.pm:63
 #, perl-format
 msgid "failed to load Markdown.pm perl module (%s) or /usr/bin/markdown (%s)"
 msgstr ""
@@ -322,11 +322,11 @@ msgstr ""
 msgid "redir cycle is not allowed"
 msgstr ""
 
-#: ../IkiWiki/Plugin/mirrorlist.pm:23
+#: ../IkiWiki/Plugin/mirrorlist.pm:35
 msgid "Mirrors"
 msgstr ""
 
-#: ../IkiWiki/Plugin/mirrorlist.pm:23
+#: ../IkiWiki/Plugin/mirrorlist.pm:35
 msgid "Mirror"
 msgstr ""
 
@@ -334,11 +334,15 @@ msgstr ""
 msgid "more"
 msgstr ""
 
-#: ../IkiWiki/Plugin/openid.pm:45
+#: ../IkiWiki/Plugin/norcs.pm:55
+msgid "getctime not implemented"
+msgstr ""
+
+#: ../IkiWiki/Plugin/openid.pm:57
 msgid "Log in with"
 msgstr ""
 
-#: ../IkiWiki/Plugin/openid.pm:48
+#: ../IkiWiki/Plugin/openid.pm:60
 msgid "Get an OpenID"
 msgstr ""
 
@@ -350,31 +354,31 @@ msgstr ""
 msgid "bad or missing template"
 msgstr ""
 
-#: ../IkiWiki/Plugin/passwordauth.pm:223
+#: ../IkiWiki/Plugin/passwordauth.pm:243
 msgid "Account creation successful. Now you can Login."
 msgstr ""
 
-#: ../IkiWiki/Plugin/passwordauth.pm:226
+#: ../IkiWiki/Plugin/passwordauth.pm:246
 msgid "Error creating account."
 msgstr ""
 
-#: ../IkiWiki/Plugin/passwordauth.pm:233
+#: ../IkiWiki/Plugin/passwordauth.pm:253
 msgid "No email address, so cannot email password reset instructions."
 msgstr ""
 
-#: ../IkiWiki/Plugin/passwordauth.pm:265
+#: ../IkiWiki/Plugin/passwordauth.pm:287
 msgid "Failed to send mail"
 msgstr ""
 
-#: ../IkiWiki/Plugin/passwordauth.pm:267
+#: ../IkiWiki/Plugin/passwordauth.pm:289
 msgid "You have been mailed password reset instructions."
 msgstr ""
 
-#: ../IkiWiki/Plugin/passwordauth.pm:302
+#: ../IkiWiki/Plugin/passwordauth.pm:324
 msgid "incorrect password reset url"
 msgstr ""
 
-#: ../IkiWiki/Plugin/passwordauth.pm:305
+#: ../IkiWiki/Plugin/passwordauth.pm:327
 msgid "password reset denied"
 msgstr ""
 
@@ -382,21 +386,21 @@ msgstr ""
 msgid "Ping received."
 msgstr ""
 
-#: ../IkiWiki/Plugin/pinger.pm:37
+#: ../IkiWiki/Plugin/pinger.pm:49
 msgid "requires 'from' and 'to' parameters"
 msgstr ""
 
-#: ../IkiWiki/Plugin/pinger.pm:42
+#: ../IkiWiki/Plugin/pinger.pm:54
 #, perl-format
 msgid "Will ping %s"
 msgstr ""
 
-#: ../IkiWiki/Plugin/pinger.pm:45
+#: ../IkiWiki/Plugin/pinger.pm:57
 #, perl-format
 msgid "Ignoring ping directive for wiki %s (this wiki is %s)"
 msgstr ""
 
-#: ../IkiWiki/Plugin/pinger.pm:61
+#: ../IkiWiki/Plugin/pinger.pm:73
 msgid "LWP not found, not pinging"
 msgstr ""
 
@@ -476,23 +480,23 @@ msgstr ""
 msgid "%A night"
 msgstr ""
 
-#: ../IkiWiki/Plugin/prettydate.pm:78
+#: ../IkiWiki/Plugin/prettydate.pm:96
 msgid "at teatime on %A"
 msgstr ""
 
-#: ../IkiWiki/Plugin/prettydate.pm:82
+#: ../IkiWiki/Plugin/prettydate.pm:100
 msgid "at midnight"
 msgstr ""
 
-#: ../IkiWiki/Plugin/prettydate.pm:85
+#: ../IkiWiki/Plugin/prettydate.pm:103
 msgid "at noon on %A"
 msgstr ""
 
-#: ../IkiWiki/Plugin/recentchanges.pm:76
+#: ../IkiWiki/Plugin/recentchanges.pm:95
 msgid "missing page"
 msgstr ""
 
-#: ../IkiWiki/Plugin/recentchanges.pm:78
+#: ../IkiWiki/Plugin/recentchanges.pm:97
 #, perl-format
 msgid "The page %s does not exist."
 msgstr ""
@@ -576,17 +580,17 @@ msgstr ""
 msgid "update for rename of %s to %s"
 msgstr ""
 
-#: ../IkiWiki/Plugin/search.pm:20
+#: ../IkiWiki/Plugin/search.pm:32
 #, perl-format
 msgid "Must specify %s when using the search plugin"
 msgstr ""
 
-#: ../IkiWiki/Plugin/search.pm:166
+#: ../IkiWiki/Plugin/search.pm:178
 #, perl-format
 msgid "need Digest::SHA1 to index %s"
 msgstr ""
 
-#: ../IkiWiki/Plugin/search.pm:201
+#: ../IkiWiki/Plugin/search.pm:213
 msgid "search"
 msgstr ""
 
@@ -688,10 +692,6 @@ msgstr ""
 msgid "failed to generate image from code"
 msgstr ""
 
-#: ../IkiWiki/Rcs/Stub.pm:96
-msgid "getctime not implemented"
-msgstr ""
-
 #: ../IkiWiki/Render.pm:276 ../IkiWiki/Render.pm:297
 #, perl-format
 msgid "skipping bad filename %s"
@@ -739,23 +739,11 @@ msgstr ""
 
 #. translators: The first parameter is a filename, and the second
 #. translators: is a (probably not translated) error message.
-#: ../IkiWiki/Setup.pm:25
+#: ../IkiWiki/Setup.pm:23
 #, perl-format
 msgid "cannot read %s: %s"
 msgstr ""
 
-#: ../IkiWiki/Setup.pm:58
-msgid "generating wrappers.."
-msgstr ""
-
-#: ../IkiWiki/Setup.pm:107
-msgid "rebuilding wiki.."
-msgstr ""
-
-#: ../IkiWiki/Setup.pm:110
-msgid "refreshing wiki.."
-msgstr ""
-
 #: ../IkiWiki/Wrapper.pm:16
 #, perl-format
 msgid "%s doesn't seem to be executable"
@@ -792,23 +780,31 @@ msgstr ""
 msgid "usage: ikiwiki [options] source dest"
 msgstr ""
 
-#: ../ikiwiki.in:82
+#: ../ikiwiki.in:79
 msgid "usage: --set var=value"
 msgstr ""
 
-#: ../IkiWiki.pm:126
+#: ../ikiwiki.in:118
+msgid "generating wrappers.."
+msgstr ""
+
+#: ../ikiwiki.in:177
+msgid "rebuilding wiki.."
+msgstr ""
+
+#: ../ikiwiki.in:180
+msgid "refreshing wiki.."
+msgstr ""
+
+#: ../IkiWiki.pm:410
 msgid "Must specify url to wiki with --url when using --cgi"
 msgstr ""
 
-#. translators: The first parameter is a
-#. translators: preprocessor directive name,
-#. translators: the second a page name, the
-#. translators: third a number.
-#: ../IkiWiki.pm:771
+#: ../IkiWiki.pm:1069
 #, perl-format
-msgid "%s preprocessing loop detected on %s at depth %i"
+msgid "preprocessing loop detected on %s at depth %i"
 msgstr ""
 
-#: ../IkiWiki.pm:1219
+#: ../IkiWiki.pm:1557
 msgid "yes"
 msgstr ""
diff --git a/t/syntax.t b/t/syntax.t
index 01346a338..694bb01df 100755
--- a/t/syntax.t
+++ b/t/syntax.t
@@ -6,7 +6,7 @@ use Test::More;
 my @progs="ikiwiki.in";
 my @libs="IkiWiki.pm";
 # monotone, external, amazon_s3 skipped since they need perl modules
-push @libs, map { chomp; $_ } `find IkiWiki -type f -name \\*.pm | grep -v IkiWiki/Rcs/monotone.pm | grep -v IkiWiki/Plugin/external.pm | grep -v IkiWiki/Plugin/amazon_s3.pm`;
+push @libs, map { chomp; $_ } `find IkiWiki -type f -name \\*.pm | grep -v monotone.pm | grep -v external.pm | grep -v amazon_s3.pm`;
 
 plan(tests => (@progs + @libs));