summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--IkiWiki.pm67
-rw-r--r--IkiWiki/Plugin/git.pm166
-rw-r--r--IkiWiki/Plugin/recentchanges.pm80
-rw-r--r--IkiWiki/Receive.pm65
-rw-r--r--debian/changelog5
-rw-r--r--doc/forum/how_do_I_revert_edits_in_the_web_mode__63__.mdwn6
-rw-r--r--doc/plugins/recentchanges.mdwn2
-rw-r--r--doc/plugins/write.mdwn30
-rw-r--r--doc/rcs.mdwn2
-rw-r--r--doc/smileys/icon-error.pngbin159 -> 397 bytes
-rw-r--r--doc/style.css3
-rw-r--r--doc/templates.mdwn2
-rw-r--r--doc/todo/web_reversion.mdwn24
-rw-r--r--doc/wikiicons/revert.pngbin0 -> 397 bytes
-rw-r--r--templates/change.tmpl12
-rw-r--r--templates/revert.tmpl21
16 files changed, 333 insertions, 152 deletions
diff --git a/IkiWiki.pm b/IkiWiki.pm
index 66ae86809..2190f008c 100644
--- a/IkiWiki.pm
+++ b/IkiWiki.pm
@@ -717,7 +717,7 @@ sub pagename ($) {
my $type=pagetype($file);
my $page=$file;
- $page=~s/\Q.$type\E*$//
+ $page=~s/\Q.$type\E*$//
if defined $type && !$hooks{htmlize}{$type}{keepextension}
&& !$hooks{htmlize}{$type}{noextension};
if ($config{indexpages} && $page=~/(.*)\/index$/) {
@@ -1124,7 +1124,7 @@ sub isselflink ($$) {
my $page=shift;
my $link=shift;
- return $page eq $link;
+ return $page eq $link;
}
sub htmllink ($$$;@) {
@@ -1519,6 +1519,69 @@ sub check_content (@) {
return defined $ok ? $ok : 1;
}
+sub check_canchange (@) {
+ my %params = @_;
+ my $cgi = $params{cgi};
+ my $session = $params{session};
+ my @changes = @{$params{changes}};
+
+ my %newfiles;
+ foreach my $change (@changes) {
+ # This untaint is safe because we check file_pruned and
+ # wiki_file_regexp.
+ my ($file)=$change->{file}=~/$config{wiki_file_regexp}/;
+ $file=possibly_foolish_untaint($file);
+ if (! defined $file || ! length $file ||
+ file_pruned($file)) {
+ error(gettext("bad file name %s"), $file);
+ }
+
+ my $type=pagetype($file);
+ my $page=pagename($file) if defined $type;
+
+ if ($change->{action} eq 'add') {
+ $newfiles{$file}=1;
+ }
+
+ if ($change->{action} eq 'change' ||
+ $change->{action} eq 'add') {
+ if (defined $page) {
+ check_canedit($page, $cgi, $session);
+ next;
+ }
+ else {
+ if (IkiWiki::Plugin::attachment->can("check_canattach")) {
+ IkiWiki::Plugin::attachment::check_canattach($session, $file, $change->{path});
+ check_canedit($file, $cgi, $session);
+ next;
+ }
+ }
+ }
+ elsif ($change->{action} eq 'remove') {
+ # check_canremove tests to see if the file is present
+ # on disk. This will fail when a single commit adds a
+ # file and then removes it again. Avoid the problem
+ # by not testing the removal in such pairs of changes.
+ # (The add is still tested, just to make sure that
+ # no data is added to the repo that a web edit
+ # could not add.)
+ next if $newfiles{$file};
+
+ if (IkiWiki::Plugin::remove->can("check_canremove")) {
+ IkiWiki::Plugin::remove::check_canremove(defined $page ? $page : $file, $cgi, $session);
+ check_canedit(defined $page ? $page : $file, $cgi, $session);
+ next;
+ }
+ }
+ else {
+ error "unknown action ".$change->{action};
+ }
+
+ error sprintf(gettext("you are not allowed to change %s"), $file);
+ }
+}
+
+
my $wikilock;
sub lockwiki () {
diff --git a/IkiWiki/Plugin/git.pm b/IkiWiki/Plugin/git.pm
index fd57ce1e4..e89813253 100644
--- a/IkiWiki/Plugin/git.pm
+++ b/IkiWiki/Plugin/git.pm
@@ -27,6 +27,8 @@ sub import {
hook(type => "rcs", id => "rcs_getctime", call => \&rcs_getctime);
hook(type => "rcs", id => "rcs_getmtime", call => \&rcs_getmtime);
hook(type => "rcs", id => "rcs_receive", call => \&rcs_receive);
+ hook(type => "rcs", id => "rcs_preprevert", call => \&rcs_preprevert);
+ hook(type => "rcs", id => "rcs_revert", call => \&rcs_revert);
}
sub checkconfig () {
@@ -718,10 +720,16 @@ sub rcs_getmtime ($) {
return findtimes($file, 0);
}
-sub rcs_receive () {
+{
+my $git_root;
+
+sub git_find_root {
# The wiki may not be the only thing in the git repo.
# Determine if it is in a subdirectory by examining the srcdir,
# and its parents, looking for the .git directory.
+
+ return $git_root if defined $git_root;
+
my $subdir="";
my $dir=$config{srcdir};
while (! -d "$dir/.git") {
@@ -732,83 +740,119 @@ sub rcs_receive () {
}
}
+ return $git_root=$subdir;
+}
+
+}
+
+sub git_parse_changes {
+ my @changes = @_;
+
+ my $subdir = git_find_root();
+ my @rets;
+ foreach my $ci (@changes) {
+ foreach my $detail (@{ $ci->{'details'} }) {
+ my $file = $detail->{'file'};
+
+ # check that all changed files are in the subdir
+ if (length $subdir &&
+ ! ($file =~ s/^\Q$subdir\E//)) {
+ error sprintf(gettext("you are not allowed to change %s"), $file);
+ }
+
+ my ($action, $mode, $path);
+ if ($detail->{'status'} =~ /^[M]+\d*$/) {
+ $action="change";
+ $mode=$detail->{'mode_to'};
+ }
+ elsif ($detail->{'status'} =~ /^[AM]+\d*$/) {
+ $action="add";
+ $mode=$detail->{'mode_to'};
+ }
+ elsif ($detail->{'status'} =~ /^[DAM]+\d*/) {
+ $action="remove";
+ $mode=$detail->{'mode_from'};
+ }
+ else {
+ error "unknown status ".$detail->{'status'};
+ }
+
+ # test that the file mode is ok
+ if ($mode !~ /^100[64][64][64]$/) {
+ error sprintf(gettext("you cannot act on a file with mode %s"), $mode);
+ }
+ if ($action eq "change") {
+ if ($detail->{'mode_from'} ne $detail->{'mode_to'}) {
+ error gettext("you are not allowed to change file modes");
+ }
+ }
+
+ # extract attachment to temp file
+ if (($action eq 'add' || $action eq 'change') &&
+ ! pagetype($file)) {
+ eval q{use File::Temp};
+ die $@ if $@;
+ my $fh;
+ ($fh, $path)=File::Temp::tempfile("XXXXXXXXXX", UNLINK => 1);
+ my $cmd = ($no_chdir ? '' : "cd $config{srcdir} && ")
+ . "git show $detail->{sha1_to} > '$path'";
+ if (system($cmd) != 0) {
+ error("failed writing temp file '$path'.");
+ }
+ }
+
+ push @rets, {
+ file => $file,
+ action => $action,
+ path => $path,
+ };
+ }
+ }
+
+ return @rets;
+}
+
+sub rcs_receive () {
my @rets;
while (<>) {
chomp;
my ($oldrev, $newrev, $refname) = split(' ', $_, 3);
-
+
# only allow changes to gitmaster_branch
if ($refname !~ /^refs\/heads\/\Q$config{gitmaster_branch}\E$/) {
error sprintf(gettext("you are not allowed to change %s"), $refname);
}
-
+
# Avoid chdir when running git here, because the changes
# are in the master git repo, not the srcdir repo.
- # The pre-recieve hook already puts us in the right place.
+ # The pre-receive hook already puts us in the right place.
$no_chdir=1;
- my @changes=git_commit_info($oldrev."..".$newrev);
+ push @rets, git_parse_changes(git_commit_info($oldrev."..".$newrev));
$no_chdir=0;
+ }
- foreach my $ci (@changes) {
- foreach my $detail (@{ $ci->{'details'} }) {
- my $file = $detail->{'file'};
+ return reverse @rets;
+}
- # check that all changed files are in the
- # subdir
- if (length $subdir &&
- ! ($file =~ s/^\Q$subdir\E//)) {
- error sprintf(gettext("you are not allowed to change %s"), $file);
- }
+sub rcs_preprevert ($) {
+ my $rev=shift;
+ my ($sha1) = $rev =~ /^($sha1_pattern)$/; # untaint
- my ($action, $mode, $path);
- if ($detail->{'status'} =~ /^[M]+\d*$/) {
- $action="change";
- $mode=$detail->{'mode_to'};
- }
- elsif ($detail->{'status'} =~ /^[AM]+\d*$/) {
- $action="add";
- $mode=$detail->{'mode_to'};
- }
- elsif ($detail->{'status'} =~ /^[DAM]+\d*/) {
- $action="remove";
- $mode=$detail->{'mode_from'};
- }
- else {
- error "unknown status ".$detail->{'status'};
- }
-
- # test that the file mode is ok
- if ($mode !~ /^100[64][64][64]$/) {
- error sprintf(gettext("you cannot act on a file with mode %s"), $mode);
- }
- if ($action eq "change") {
- if ($detail->{'mode_from'} ne $detail->{'mode_to'}) {
- error gettext("you are not allowed to change file modes");
- }
- }
-
- # extract attachment to temp file
- if (($action eq 'add' || $action eq 'change') &&
- ! pagetype($file)) {
- eval q{use File::Temp};
- die $@ if $@;
- my $fh;
- ($fh, $path)=File::Temp::tempfile("XXXXXXXXXX", UNLINK => 1);
- if (system("git show ".$detail->{sha1_to}." > '$path'") != 0) {
- error("failed writing temp file");
- }
- }
+ return git_parse_changes(git_commit_info($sha1, 1));
+}
- push @rets, {
- file => $file,
- action => $action,
- path => $path,
- };
- }
- }
- }
+sub rcs_revert ($) {
+ # Try to revert the given rev; returns undef on _success_.
+ my $rev = shift;
+ my ($sha1) = $rev =~ /^($sha1_pattern)$/; # untaint
- return reverse @rets;
+ if (run_or_non('git', 'revert', '--no-commit', $sha1)) {
+ return undef;
+ }
+ else {
+ run_or_die('git', 'reset', '--hard');
+ return sprintf(gettext("Failed to revert commit %s"), $sha1);
+ }
}
1
diff --git a/IkiWiki/Plugin/recentchanges.pm b/IkiWiki/Plugin/recentchanges.pm
index 758b98348..562f61d40 100644
--- a/IkiWiki/Plugin/recentchanges.pm
+++ b/IkiWiki/Plugin/recentchanges.pm
@@ -13,6 +13,7 @@ sub import {
hook(type => "refresh", id => "recentchanges", call => \&refresh);
hook(type => "pagetemplate", id => "recentchanges", call => \&pagetemplate);
hook(type => "htmlize", id => "_change", call => \&htmlize);
+ hook(type => "sessioncgi", id => "recentchanges", call => \&sessioncgi);
# Load goto to fix up links from recentchanges
IkiWiki::loadplugin("goto");
}
@@ -60,6 +61,76 @@ sub refresh ($) {
}
}
+sub sessioncgi ($$) {
+ my ($q, $session) = @_;
+ my $do = $q->param('do');
+ my $rev = $q->param('rev');
+
+ return unless $do eq 'revert' && $rev;
+
+ my @changes=$IkiWiki::hooks{rcs}{rcs_preprevert}{call}->($rev);
+ IkiWiki::check_canchange(
+ cgi => $q,
+ session => $session,
+ changes => \@changes,
+ );
+
+ eval q{use CGI::FormBuilder};
+ error($@) if $@;
+ my $form = CGI::FormBuilder->new(
+ name => "revert",
+ header => 0,
+ charset => "utf-8",
+ method => 'POST',
+ javascript => 0,
+ params => $q,
+ action => $config{cgiurl},
+ stylesheet => 1,
+ template => { template('revert.tmpl') },
+ fields => [qw{revertmessage do sid rev}],
+ );
+ my $buttons=["Revert", "Cancel"];
+
+ $form->field(name => "revertmessage", type => "text", size => 80);
+ $form->field(name => "sid", type => "hidden", value => $session->id,
+ force => 1);
+ $form->field(name => "do", type => "hidden", value => "revert",
+ force => 1);
+
+ IkiWiki::decode_form_utf8($form);
+
+ if ($form->submitted eq 'Revert' && $form->validate) {
+ IkiWiki::checksessionexpiry($q, $session, $q->param('sid'));
+ my $message=sprintf(gettext("This reverts commit %s"), $rev);
+ if (defined $form->field('revertmessage') &&
+ length $form->field('revertmessage')) {
+ $message=$form->field('revertmessage')."\n\n".$message;
+ }
+ my $r = $IkiWiki::hooks{rcs}{rcs_revert}{call}->($rev);
+ error $r if defined $r;
+ IkiWiki::disable_commit_hook();
+ IkiWiki::rcs_commit_staged(
+ message => $message,
+ session => $session,
+ );
+ IkiWiki::enable_commit_hook();
+
+ require IkiWiki::Render;
+ IkiWiki::refresh();
+ IkiWiki::saveindex();
+ }
+ elsif ($form->submitted ne 'Cancel') {
+ $form->title(sprintf(gettext("confirm reversion of %s"), $rev));
+ $form->tmpl_param(diff => encode_entities(scalar IkiWiki::rcs_diff($rev)));
+ $form->field(name => "rev", type => "hidden", value => $rev, force => 1);
+ IkiWiki::showform($form, $buttons, $session, $q);
+ exit 0;
+ }
+
+ IkiWiki::redirect($q, urlto($config{recentchangespage}, ''));
+ exit 0;
+}
+
# Enable the recentchanges link.
sub pagetemplate (@) {
my %params=@_;
@@ -113,6 +184,15 @@ sub store ($$$) {
} @{$change->{pages}}
];
push @{$change->{pages}}, { link => '...' } if $is_excess;
+
+ if (length $config{cgiurl} &&
+ exists $IkiWiki::hooks{rcs}{rcs_preprevert} &&
+ exists $IkiWiki::hooks{rcs}{rcs_revert}) {
+ $change->{reverturl} = IkiWiki::cgiurl(
+ do => "revert",
+ rev => $change->{rev}
+ );
+ }
$change->{author}=$change->{user};
my $oiduser=eval { IkiWiki::openiduser($change->{user}) };
diff --git a/IkiWiki/Receive.pm b/IkiWiki/Receive.pm
index e77c477a9..225f2b9ab 100644
--- a/IkiWiki/Receive.pm
+++ b/IkiWiki/Receive.pm
@@ -48,10 +48,10 @@ EOF
sub test () {
exit 0 if trusted();
-
+
IkiWiki::lockwiki();
IkiWiki::loadindex();
-
+
# Dummy up a cgi environment to use when calling check_canedit
# and friends.
eval q{use CGI};
@@ -72,63 +72,12 @@ sub test () {
regdate => time,
}) || error("failed adding user");
}
-
- my %newfiles;
-
- foreach my $change (IkiWiki::rcs_receive()) {
- # This untaint is safe because we check file_pruned and
- # wiki_file_regexp.
- my ($file)=$change->{file}=~/$config{wiki_file_regexp}/;
- $file=IkiWiki::possibly_foolish_untaint($file);
- if (! defined $file || ! length $file ||
- IkiWiki::file_pruned($file)) {
- error(gettext("bad file name %s"), $file);
- }
-
- my $type=pagetype($file);
- my $page=pagename($file) if defined $type;
-
- if ($change->{action} eq 'add') {
- $newfiles{$file}=1;
- }
-
- if ($change->{action} eq 'change' ||
- $change->{action} eq 'add') {
- if (defined $page) {
- IkiWiki::check_canedit($page, $cgi, $session);
- next;
- }
- else {
- if (IkiWiki::Plugin::attachment->can("check_canattach")) {
- IkiWiki::Plugin::attachment::check_canattach($session, $file, $change->{path});
- IkiWiki::check_canedit($file, $cgi, $session);
- next;
- }
- }
- }
- elsif ($change->{action} eq 'remove') {
- # check_canremove tests to see if the file is present
- # on disk. This will fail when a single commit adds a
- # file and then removes it again. Avoid the problem
- # by not testing the removal in such pairs of changes.
- # (The add is still tested, just to make sure that
- # no data is added to the repo that a web edit
- # could not add.)
- next if $newfiles{$file};
-
- if (IkiWiki::Plugin::remove->can("check_canremove")) {
- IkiWiki::Plugin::remove::check_canremove(defined $page ? $page : $file, $cgi, $session);
- IkiWiki::check_canedit(defined $page ? $page : $file, $cgi, $session);
- next;
- }
- }
- else {
- error "unknown action ".$change->{action};
- }
-
- error sprintf(gettext("you are not allowed to change %s"), $file);
- }
+ check_canchange(
+ cgi => $cgi,
+ session => $session,
+ changes => [IkiWiki::rcs_receive()]
+ );
exit 0;
}
diff --git a/debian/changelog b/debian/changelog
index 1d12d0558..d1ee4967d 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -4,6 +4,11 @@ ikiwiki (3.20100927) UNRELEASED; urgency=low
* htmltidy: Allow configuring tidy parameters in setup file.
(W. Trevor King)
* Updated French program translation. Closes: #598918
+ * git: Added new rcs_revert and rcs_preprevert hooks.
+ * recentchanges: Add revert buttons to RecentChanges page, and
+ implement web-based reversion interface.
+ * Thanks to Peter Gammie for his assistance with the web-based reversion
+ feature.
-- Joey Hess <joeyh@debian.org> Wed, 29 Sep 2010 11:58:23 -0400
diff --git a/doc/forum/how_do_I_revert_edits_in_the_web_mode__63__.mdwn b/doc/forum/how_do_I_revert_edits_in_the_web_mode__63__.mdwn
index 4998c1a75..d69b3801b 100644
--- a/doc/forum/how_do_I_revert_edits_in_the_web_mode__63__.mdwn
+++ b/doc/forum/how_do_I_revert_edits_in_the_web_mode__63__.mdwn
@@ -38,3 +38,9 @@ Puzzled a bit :-/
Perer Gammie and I are working on reversion over at [[todo/web_reversion]].
--[[Joey]]
+
+Update: Web reversion is now supported by ikiwiki. Only changes committed
+to your wiki after you upgrade to the version of ikiwiki that supports it
+will get revert buttons on the RecentChanges page. If you want to force
+adding buttons for older changes, you can delete `recentchanges/*._change`
+from your srcdir, and rebuild the wiki. --[[Joey]]
diff --git a/doc/plugins/recentchanges.mdwn b/doc/plugins/recentchanges.mdwn
index 823f68502..6fff18e8a 100644
--- a/doc/plugins/recentchanges.mdwn
+++ b/doc/plugins/recentchanges.mdwn
@@ -6,6 +6,8 @@ generates a page describing each recent change made to the wiki. These
pages can be joined together with [[inline]] to generate the
[[RecentChanges]] page.
+This plugin also currently handles web-based reversion of changes.
+
Typically only the RecentChanges page will use the pages generated by this
plugin, but you can use it elsewhere too if you like. It's used like this:
diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn
index d5bd1dd76..6b751f0cd 100644
--- a/doc/plugins/write.mdwn
+++ b/doc/plugins/write.mdwn
@@ -1154,8 +1154,6 @@ context, and the whole diff in scalar context.
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.
-
If the RCS cannot determine a ctime for the file, return 0.
#### `rcs_getmtime($)`
@@ -1176,9 +1174,9 @@ sense to implement for all RCSs.
It should examine the incoming changes, and do any sanity
checks that are appropriate for the RCS to limit changes to safe file adds,
-removes, and changes. If something bad is found, it should exit
-nonzero, to abort the push. Otherwise, it should return a list of
-files that were changed, in the form:
+removes, and changes. If something bad is found, it should die, to abort
+the push. Otherwise, it should return a list of files that were changed,
+in the form:
{
file => # name of file that was changed
@@ -1191,6 +1189,28 @@ files that were changed, in the form:
The list will then be checked to make sure that each change is one that
is allowed to be made via the web interface.
+#### `rcs_preprevert($)`
+
+This is called by the revert web interface. It is passed a RCS-specific
+change ID, and should determine what the effects would be of reverting
+that change, and return the same data structure as `rcs_receive`.
+
+Like `rcs_receive`, it should do whatever sanity checks are appropriate
+for the RCS to limit changes to safe changes, and die if a change would
+be unsafe to revert.
+
+#### `rcs_revert($)`
+
+This is called by the revert web interface. It is passed a named
+parameter rev that is the RCS-specific change ID to revert.
+
+It should try to revert the specified rev, and leave the reversion staged
+so `rcs_commit_staged` will complete it. It should return undef on _success_
+and an error message on failure.
+
+This hook and `rcs_preprevert` are optional, if not implemented, no revert
+web interface will be available.
+
### PageSpec plugins
It's also possible to write plugins that add new functions to
diff --git a/doc/rcs.mdwn b/doc/rcs.mdwn
index 83c6910d0..06af9807c 100644
--- a/doc/rcs.mdwn
+++ b/doc/rcs.mdwn
@@ -23,6 +23,8 @@ auto.setup |yes |yes |incomplete|yes |incomplete |yes
`rcs_diff` |yes |yes |yes |yes |no |yes |yes |yes
`rcs_getctime` |fast |slow |slow |slow |slow |slow |slow |slow
`rcs_getmtime` |fast |slow |slow |no |no |no |no |no
+`rcs_preprevert` |yes |no |no |no |no |no |no |no
+`rcs_revert` |yes |no |no |no |no |no |no |no
anonymous push |yes |no |no |no |no |no |no |no
conflict handling |yes |yes |yes |buggy |yes |yes |yes |yes
openid username |yes |no |no |no |no |no |no |no
diff --git a/doc/smileys/icon-error.png b/doc/smileys/icon-error.png
index 53b1055f6..c39e65c33 100644
--- a/doc/smileys/icon-error.png
+++ b/doc/smileys/icon-error.png
Binary files differ
diff --git a/doc/style.css b/doc/style.css
index fa4b2a38a..aa27d8866 100644
--- a/doc/style.css
+++ b/doc/style.css
@@ -172,7 +172,8 @@ div.recentchanges {
width: 35%;
font-size: small;
}
-.recentchanges .pagelinks {
+.recentchanges .pagelinks,
+.recentchanges .revert {
float: right;
margin: 0;
width: 60%;
diff --git a/doc/templates.mdwn b/doc/templates.mdwn
index bfb6a439a..4fd2bf501 100644
--- a/doc/templates.mdwn
+++ b/doc/templates.mdwn
@@ -74,7 +74,7 @@ html out of ikiwiki and in the templates.
* `editpage.tmpl`, `editconflict.tmpl`, `editcreationconflict.tmpl`,
`editfailedsave.tmpl`, `editpagegone.tmpl`, `pocreatepage.tmpl`,
`editcomment.tmpl` `commentmoderation.tmpl`, `renamesummary.tmpl`,
- `passwordmail.tmpl`, `openid-selector.tmpl` - Parts of ikiwiki's user
+ `passwordmail.tmpl`, `openid-selector.tmpl`, `revert.tmpl` - Parts of ikiwiki's user
interface; do not normally need to be customised.
[[!meta robots="noindex, follow"]]
diff --git a/doc/todo/web_reversion.mdwn b/doc/todo/web_reversion.mdwn
index 33fa79aad..9550fa58b 100644
--- a/doc/todo/web_reversion.mdwn
+++ b/doc/todo/web_reversion.mdwn
@@ -55,7 +55,6 @@ Peter Gammie has done an initial implementation of the above.
> (The data from `rcs_preprevert` could also be used for a confirmation
> prompt -- it doesn't currently include enough info for diffs, but at
> least could have a list of changed files.)
->> I added `rcs_showpatch` which simply yields the output of `git show <patch-id>`. -- [[peteg]]
>
> Note that it's possible for a git repo to have commits that modify wiki
> files in a subdir, and code files elsewhere. `rcs_preprevert` should
@@ -63,29 +62,14 @@ Peter Gammie has done an initial implementation of the above.
>> Taken care of by refactoring `rcs_receive` in `git.pm`
>> I've tested it lightly in my single-user setup. It's a little nasty that the `attachment` plugin
>> gets used to check whether attachments are allowed -- there really should be a hook for that.
+>>> I agree, but have not figured out a way to make a hook work yet.
+>>> --[[Joey]]
>>
>> Please look it over and tell me what else needs fixing... -- [[peteg]]
>>> I have made my own revert branch and put a few fixes in there
>>> [[!template id=gitbranch branch=origin/revert author="[[joey]]"]]
->>> (and fixed all the indention..). Issues I noticed but have not gotten
->>> to: --[[Joey]]
+>>> (and fixed all the indention..). --[[Joey]]
>>>> Please change the git pointer above, then. I will work on your branch. -- [[peteg]]
->>>
->>> * `rcs_diff` already exists; why add `rcs_showpatch`?
->>>> If `rcs_diff` is intended for human consumption, by all means we can use that. -- [[peteg]]
->>> * Would it be better for `rcs_revert` to not commit, and
->>> `rcs_commit_staged` to then be used? This would work for git, but
->>> maybe other RCSs would be problimatic. It would simplifiy the
->>> interface and allow for future mulitple-revert interfaces.
->>> * I quite don't understand why one caller of `git_parse_changes`
->>> needs it to chdir, and not the other one. It's running
->>> in the same git repo either way, and git doesn't need
->>> `git show` to run in a subdir at all..
->>>> I was aping (preserving) what was already there. I don't understand what you say about `git show` - it must run under $srcdir, surely? And empirically the CGI process wasn't in the right place. By all means simplify that. -- [[peteg]]
-
->>> * Probably needs to untaint the revs passed in.
->>> * Seems backwards for `rcs_preprevert` to import and
->>> use `IkiWiki::Receive`.
->>>> Indeed. This is saying that the checking code in IkiWiki::Receive is in the wrong place. I think it would be better to set up some general hooks and shuffle it into a plugin, for then other plugins that maintain special files in the repo can have a say about validity. -- [[peteg]]
+[[done]]
diff --git a/doc/wikiicons/revert.png b/doc/wikiicons/revert.png
new file mode 100644
index 000000000..c39e65c33
--- /dev/null
+++ b/doc/wikiicons/revert.png
Binary files differ
diff --git a/templates/change.tmpl b/templates/change.tmpl
index 671b9e483..60a9d94b5 100644
--- a/templates/change.tmpl
+++ b/templates/change.tmpl
@@ -10,10 +10,8 @@
<span class="desc"><br />Changed pages:</span>
<span class="pagelinks">
<TMPL_LOOP PAGES>
-<TMPL_IF DIFFURL><a href="<TMPL_VAR DIFFURL>">[[diff|wikiicons/diff.png]]</a><TMPL_VAR LINK>
-<TMPL_ELSE>
-<TMPL_VAR LINK>
-</TMPL_IF>
+<TMPL_IF DIFFURL><a href="<TMPL_VAR DIFFURL>" title="diff" rel="nofollow">[[diff|wikiicons/diff.png]]</a><TMPL_VAR LINK>
+<TMPL_ELSE><TMPL_VAR LINK></TMPL_IF>
</TMPL_LOOP>
</span>
<span class="desc"><br />Changed by:</span>
@@ -28,7 +26,13 @@
<span class="committype"><TMPL_VAR COMMITTYPE></span>
<span class="desc"><br />Date:</span>
<span class="changedate"><TMPL_VAR COMMITDATE></span>
+<span class="desc"><br /></span>
</div>
+<TMPL_IF REVERTURL>
+<span class="revert">
+<a href="<TMPL_VAR REVERTURL>" title="revert" rel="nofollow">[[revert|wikiicons/revert.png]]</a>
+</span>
+</TMPL_IF>
<div class="changelog">
<TMPL_LOOP MESSAGE>
<TMPL_IF LINE>
diff --git a/templates/revert.tmpl b/templates/revert.tmpl
new file mode 100644
index 000000000..0de32811b
--- /dev/null
+++ b/templates/revert.tmpl
@@ -0,0 +1,21 @@
+<TMPL_VAR FORM-START>
+<div>
+ <TMPL_VAR FIELD-DO>
+ <TMPL_VAR FIELD-SID>
+ <TMPL_VAR FIELD-REV>
+<label for="revertmessage" class="block">Optional comment about this change:</label>
+ <TMPL_VAR FIELD-REVERTMESSAGE>
+</div>
+<div class="revert buttons">
+<TMPL_VAR form-submit>
+<TMPL_VAR form-cancel>
+</div>
+<TMPL_VAR FORM-END>
+<br \>
+<div class="header">
+<span>Diff being reverted:</span>
+</div>
+<div id="diff">
+<pre>
+<TMPL_VAR diff>
+</pre>