X-Git-Url: https://sipb.mit.edu/gitweb.cgi/ikiwiki.git/blobdiff_plain/2885278ebd2e2bfad188a583d7ad5d3c8b7c3a21..53146f395cf979f93b9d53607d71993d1fbc7fed:/ikiwiki diff --git a/ikiwiki b/ikiwiki index d6057c101..db0056aa4 100755 --- a/ikiwiki +++ b/ikiwiki @@ -10,7 +10,8 @@ use Getopt::Long; my (%links, %oldlinks, %oldpagemtime, %renderedfiles, %pagesources); -my %config=( #{{{ +# Holds global config settings, also used by some modules. +our %config=( #{{{ wiki_file_prune_regexp => qr{((^|/).svn/|\.\.|^\.|\/\.|\.html?$)}, wiki_link_regexp => qr/\[\[([^\s]+)\]\]/, wiki_file_regexp => qr/(^[-A-Za-z0-9_.:\/+]+$)/, @@ -165,14 +166,17 @@ sub writefile ($$) { #{{{ close OUT; } #}}} -sub findlinks ($) { #{{{ +sub findlinks ($$) { #{{{ my $content=shift; + my $page=shift; my @links; while ($content =~ /(??$link" - } - else { - return "?$link" - } + return "?$link" } $bestlink=File::Spec->abs2rel($bestlink, dirname($page)); @@ -242,10 +247,10 @@ sub htmllink { #{{{ sub linkify ($$) { #{{{ my $content=shift; - my $file=shift; + my $page=shift; $content =~ s{(\\?)$config{wiki_link_regexp}}{ - $1 ? "[[$2]]" : htmllink(pagename($file), $2) + $1 ? "[[$2]]" : htmllink($page, $2) }eg; return $content; @@ -367,7 +372,7 @@ sub check_overwrite ($$) { #{{{ ", before, so not rendering from $src"); } } #}}} - + sub render ($) { #{{{ my $file=shift; @@ -376,9 +381,9 @@ sub render ($) { #{{{ if ($type ne 'unknown') { my $page=pagename($file); - $links{$page}=[findlinks($content)]; + $links{$page}=[findlinks($content, $page)]; - $content=linkify($content, $file); + $content=linkify($content, $page); $content=htmlize($type, $content); $content=finalize($content, $page); @@ -396,6 +401,29 @@ sub render ($) { #{{{ } } #}}} +sub lockwiki () { #{{{ + # Take an exclusive lock on the wiki to prevent multiple concurrent + # run issues. The lock will be dropped on program exit. + if (! -d "$config{srcdir}/.ikiwiki") { + mkdir("$config{srcdir}/.ikiwiki"); + } + open(WIKILOCK, ">$config{srcdir}/.ikiwiki/lockfile") || error ("cannot write to lockfile: $!"); + if (! flock(WIKILOCK, 2 | 4)) { + debug("wiki seems to be locked, waiting for lock"); + my $wait=600; # arbitrary, but don't hang forever to + # prevent process pileup + for (1..600) { + return if flock(WIKILOCK, 2 | 4); + sleep 1; + } + error("wiki is locked; waited $wait seconds without lock being freed (possible stuck process or stale lock?)"); + } +} #}}} + +sub unlockwiki () { #{{{ + close WIKILOCK; +} #}}} + sub loadindex () { #{{{ open (IN, "$config{srcdir}/.ikiwiki/index") || return; while () { @@ -433,19 +461,34 @@ sub rcs_update () { #{{{ } } #}}} -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 $message=shift; + my $file=shift; if (-d "$config{srcdir}/.svn") { + # svn up to let svn merge in other changes + if (system("svn", "update", "$config{srcdir}/$file") != 0) { + warn("svn update failed\n"); + } if (system("svn", "commit", "--quiet", "-m", possibly_foolish_untaint($message), - $config{srcdir}) != 0) { + "$config{srcdir}/$file") != 0) { warn("svn commit failed\n"); + my $conflict=readfile("$config{srcdir}/$file"); + if (system("svn", "revert", "$config{srcdir}/$file") != 0) { + warn("svn revert failed\n"); + } + return $conflict; } } + return undef # success } #}}} sub rcs_add ($) { #{{{ + # filename is relative to the root of the srcdir my $file=shift; if (-d "$config{srcdir}/.svn") { @@ -465,6 +508,7 @@ sub rcs_recentchanges ($) { #{{{ my $num=shift; my @ret; + eval q{use CGI 'escapeHTML'}; eval q{use Date::Parse}; eval q{use Time::Duration}; @@ -480,7 +524,7 @@ sub rcs_recentchanges ($) { #{{{ my $infoline=qr/^r(\d+)\s+\|\s+([^\s]+)\s+\|\s+(\d+-\d+-\d+\s+\d+:\d+:\d+\s+[-+]?\d+).*/; my $state='start'; my ($rev, $user, $when, @pages, @message); - foreach (`LANG=C svn log -v '$svn_url'`) { + foreach (`LANG=C svn log --limit $num -v '$svn_url'`) { chomp; if ($state eq 'start' && /$div/) { $state='header'; @@ -520,7 +564,7 @@ sub rcs_recentchanges ($) { #{{{ @pages=@message=(); } elsif ($state eq 'body') { - push @message, {line => $_}, + push @message, {line => escapeHTML($_)}, } } } @@ -539,10 +583,9 @@ sub prune ($) { #{{{ } #}}} sub refresh () { #{{{ - # Find existing pages. + # find existing pages my %exists; my @files; - eval q{use File::Find}; find({ no_chdir => 1, @@ -552,7 +595,7 @@ sub refresh () { #{{{ $File::Find::prune=1; use warnings "all"; } - elsif (! -d $_) { + elsif (! -d $_ && ! -l $_) { my ($f)=/$config{wiki_file_regexp}/; # untaint if (! defined $f) { warn("skipping bad filename $_\n"); @@ -583,7 +626,7 @@ sub refresh () { #{{{ foreach my $page (keys %oldpagemtime) { if (! $exists{$page}) { debug("removing old page $page"); - push @del, $renderedfiles{$page}; + push @del, $pagesources{$page}; prune($config{destdir}."/".$renderedfiles{$page}); delete $renderedfiles{$page}; $oldpagemtime{$page}=0; @@ -635,8 +678,7 @@ FILE: foreach my $file (@files) { foreach my $file (keys %rendered, @del) { my $page=pagename($file); if (exists $links{$page}) { - foreach my $link (@{$links{$page}}) { - $link=bestlink($page, $link); + foreach my $link (map { bestlink($page, $_) } @{$links{$page}}) { if (length $link && ! exists $oldlinks{$page} || ! grep { $_ eq $link } @{$oldlinks{$page}}) { @@ -645,8 +687,7 @@ FILE: foreach my $file (@files) { } } if (exists $oldlinks{$page}) { - foreach my $link (@{$oldlinks{$page}}) { - $link=bestlink($page, $link); + foreach my $link (map { bestlink($page, $_) } @{$oldlinks{$page}}) { if (length $link && ! exists $links{$page} || ! grep { $_ eq $link } @{$links{$page}}) { @@ -990,6 +1031,7 @@ sub cgi_editpage ($$) { #{{{ table => 0, template => "$config{templatedir}/editpage.tmpl" ); + my @buttons=("Save Page", "Preview", "Cancel"); my ($page)=$form->param('page')=~/$config{wiki_file_regexp}/; if (! defined $page || ! length $page || $page ne $q->param('page') || @@ -1009,7 +1051,18 @@ sub cgi_editpage ($$) { #{{{ print $q->redirect("$config{url}/".htmlpage($page)); return; } - if (! $form->submitted || ! $form->validate) { + elsif ($form->submitted eq "Preview") { + $form->tmpl_param("page_preview", + htmlize($config{default_pageext}, + linkify($form->field('content'), $page))); + } + else { + $form->tmpl_param("page_preview", ""); + } + $form->tmpl_param("page_conflict", ""); + + if (! $form->submitted || $form->submitted eq "Preview" || + ! $form->validate) { if ($form->field("do") eq "create") { if (exists $pagesources{lc($page)}) { # hmm, someone else made the page in the @@ -1019,44 +1072,51 @@ sub cgi_editpage ($$) { #{{{ } my @page_locs; + my $best_loc; my ($from)=$form->param('from')=~/$config{wiki_file_regexp}/; if (! defined $from || ! length $from || $from ne $form->param('from') || $from=~/$config{wiki_file_prune_regexp}/ || $from=~/^\//) { - @page_locs=$page; + @page_locs=$best_loc=$page; } else { my $dir=$from."/"; $dir=~s![^/]+/$!!; push @page_locs, $dir.$page; push @page_locs, "$from/$page"; + $best_loc="$from/$page"; while (length $dir) { $dir=~s![^/]+/$!!; push @page_locs, $dir.$page; } + + @page_locs = grep { ! exists + $pagesources{lc($_)} } @page_locs; } $form->tmpl_param("page_select", 1); $form->field(name => "page", type => 'select', - options => \@page_locs); + options => \@page_locs, value => $best_loc); $form->title("creating $page"); } elsif ($form->field("do") eq "edit") { - my $content=""; - if (exists $pagesources{lc($page)}) { - $content=readfile("$config{srcdir}/$pagesources{lc($page)}"); - $content=~s/\n/\r\n/g; + if (! length $form->field('content')) { + my $content=""; + if (exists $pagesources{lc($page)}) { + $content=readfile("$config{srcdir}/$pagesources{lc($page)}"); + $content=~s/\n/\r\n/g; + } + $form->field(name => "content", value => $content, + force => 1); } $form->tmpl_param("page_select", 0); - $form->field(name => "content", value => $content, - force => 1); $form->field(name => "page", type => 'hidden'); $form->title("editing $page"); } $form->tmpl_param("can_commit", $config{svn}); $form->tmpl_param("indexlink", indexlink()); - print $form->render(submit => ["Save Page", "Cancel"]); + print $form->render(submit => \@buttons); } else { # save page @@ -1088,9 +1148,19 @@ sub cgi_editpage ($$) { #{{{ if ($newfile) { rcs_add($file); } + # prevent deadlock with post-commit hook + unlockwiki(); # presumably the commit will trigger an update # of the wiki - rcs_commit($message); + my $conflict=rcs_commit($file, $message); + + if (defined $conflict) { + $form->tmpl_param("page_conflict", 1); + $form->field("content", $conflict); + $form->field("do", "edit)"); + print $form->render(submit => \@buttons); + return; + } } else { loadindex(); @@ -1158,25 +1228,13 @@ sub setup () { # {{{ ($code)=$code=~/(.*)/s; close IN; - my (%setup); eval $code; error($@) if $@; - - gen_wrapper(%config, %setup, %{$setup{cgiwrapper}}) if $setup{cgiwrapper}; - gen_wrapper(%config, %setup, %{$setup{svnwrapper}}) if $setup{svnwrapper}; - - print "$setup{wikiname} setup complete, now forcing a rebuild\n"; - foreach my $c (keys %setup) { - $config{$c}=possibly_foolish_untaint($setup{$c}) - if defined $setup{$c} && ! ref $setup{$c}; - } - $config{rebuild}=1; - refresh(); - saveindex(); exit; } #}}} # main {{{ +lockwiki(); setup() if $config{setup}; if ($config{wrapper}) { gen_wrapper(%config);