Merge branch 'darcs'

Conflicts:
	debian/changelog
master
Joey Hess 2009-04-04 18:38:16 -04:00
commit 760e100947
10 changed files with 539 additions and 610 deletions

View File

@ -0,0 +1,439 @@
#!/usr/bin/perl
package IkiWiki::Plugin::darcs;
use warnings;
use strict;
use IkiWiki;
sub import {
hook(type => "checkconfig", id => "darcs", call => \&checkconfig);
hook(type => "getsetup", id => "darcs", 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 silentsystem (@) {
open(SAVED_STDOUT, ">&STDOUT");
open(STDOUT, ">/dev/null");
my $ret = system @_;
open(STDOUT, ">&SAVED_STDOUT");
return $ret;
}
sub darcs_info ($$$) {
my $field = shift;
my $repodir = shift;
my $file = shift; # Relative to the repodir.
my $child = open(DARCS_CHANGES, "-|");
if (! $child) {
exec('darcs', 'changes', '--repodir', $repodir, '--xml-output', $file) or
error("failed to run 'darcs changes'");
}
# Brute force for now. :-/
while (<DARCS_CHANGES>) {
last if /^<\/created_as>$/;
}
($_) = <DARCS_CHANGES> =~ /$field=\'([^\']+)/;
$field eq 'hash' and s/\.gz//; # Strip away the '.gz' from 'hash'es.
close(DARCS_CHANGES);
return $_;
}
sub file_in_vc($$) {
my $repodir = shift;
my $file = shift;
my $child = open(DARCS_MANIFEST, "-|");
if (! $child) {
exec('darcs', 'query', 'manifest', '--repodir', $repodir) or
error("failed to run 'darcs query manifest'");
}
my $found=0;
while (<DARCS_MANIFEST>) {
$found = 1, last if /^(\.\/)?$file$/;
}
close(DARCS_MANIFEST) or error("'darcs query manifest' exited " . $?);
return $found;
}
sub darcs_rev($) {
my $file = shift; # Relative to the repodir.
my $repodir = $config{srcdir};
return "" if (! file_in_vc($repodir, $file));
my $hash = darcs_info('hash', $repodir, $file);
return defined $hash ? $hash : "";
}
sub checkconfig() {
if (defined $config{darcs_wrapper} && length $config{darcs_wrapper}) {
push @{$config{wrappers}}, {
wrapper => $config{darcs_wrapper},
wrappermode => (defined $config{darcs_wrappermode} ? $config{darcs_wrappermode} : "06755"),
};
}
}
sub getsetup() {
return
plugin => {
safe => 0, # rcs plugin
rebuild => undef,
},
darcs_wrapper => {
type => "string",
example => "/darcs/repo/_darcs/ikiwiki-wrapper",
description => "wrapper to generate (set as master repo apply hook)",
safe => 0, # file
rebuild => 0,
},
darcs_wrappermode => {
type => "string",
example => '06755',
description => "mode for darcs_wrapper (can safely be made suid)",
safe => 0,
rebuild => 0,
},
historyurl => {
type => "string",
example => "http://darcs.example.com/darcsweb.cgi?r=wiki;a=filehistory;f=[[file]]",
description => "darcsweb url to show file history ([[file]] substituted)",
safe => 1,
rebuild => 1,
},
diffurl => {
type => "string",
example => "http://darcs.example.com/darcsweb.cgi?r=wiki;a=filediff;h=[[hash]];f=[[file]]",
description => "darcsweb url to show a diff ([[hash]] and [[file]] substituted)",
safe => 1,
rebuild => 1,
},
}
sub rcs_update () {
silentsystem('darcs', "pull", "--repodir", $config{srcdir}, "-qa")
}
sub rcs_prepedit ($) {
# Prepares to edit a file under revision control. Returns a token that
# must be passed to rcs_commit() when the file is to be commited. For us,
# this token the hash value of the latest patch that modifies the file,
# i.e. something like its current revision.
my $file = shift; # Relative to the repodir.
my $rev = darcs_rev($file);
return $rev;
}
sub rcs_commit ($$$;$$) {
# Commit the page. Returns 'undef' on success and a version of the page
# with conflict markers on failure.
my ($file, $message, $rcstoken, $user, $ipaddr) = @_;
# Compute if the "revision" of $file changed.
my $changed = darcs_rev($file) ne $rcstoken;
# Yes, the following is a bit convoluted.
if ($changed) {
# TODO. Invent a better, non-conflicting name.
rename("$config{srcdir}/$file", "$config{srcdir}/$file.save") or
error("failed to rename $file to $file.save: $!");
# Roll the repository back to $rcstoken.
# TODO. Can we be sure that no changes are lost? I think that
# we can, if we make sure that the 'darcs push' below will always
# succeed.
# We need to revert everything as 'darcs obliterate' might choke
# otherwise.
# TODO: 'yes | ...' needed? Doesn't seem so.
silentsystem('darcs', "revert", "--repodir", $config{srcdir}, "--all") == 0 ||
error("'darcs revert' failed");
# Remove all patches starting at $rcstoken.
my $child = open(DARCS_OBLITERATE, "|-");
if (! $child) {
open(STDOUT, ">/dev/null");
exec('darcs', "obliterate", "--repodir", $config{srcdir},
"--match", "hash " . $rcstoken) and
error("'darcs obliterate' failed");
}
1 while print DARCS_OBLITERATE "y";
close(DARCS_OBLITERATE);
# Restore the $rcstoken one.
silentsystem('darcs', "pull", "--quiet", "--repodir", $config{srcdir},
"--match", "hash " . $rcstoken, "--all") == 0 ||
error("'darcs pull' failed");
# We're back at $rcstoken. Re-install the modified file.
rename("$config{srcdir}/$file.save", "$config{srcdir}/$file") or
error("failed to rename $file.save to $file: $!");
}
# Record the changes.
my $author;
if (defined $user) {
$author = "$user\@web";
}
elsif (defined $ipaddr) {
$author = "$ipaddr\@web";
}
else {
$author = "anon\@web";
}
if (!defined $message || !length($message)) {
$message = "empty message";
}
silentsystem('darcs', 'record', '--repodir', $config{srcdir}, '--all',
'-m', $message, '--author', $author, $file) == 0 ||
error("'darcs record' failed");
# Update the repository by pulling from the default repository, which is
# master repository.
silentsystem('darcs', "pull", "--quiet", "--repodir", $config{srcdir},
"--all") == 0 || error("'darcs pull' failed");
# If this updating yields any conflicts, we'll record them now to resolve
# them. If nothing is recorded, there are no conflicts.
$rcstoken = darcs_rev($file);
# TODO: Use only the first line here, i.e. only the patch name?
writefile("$file.log", $config{srcdir}, 'resolve conflicts: ' . $message);
silentsystem('darcs', 'record', '--repodir', $config{srcdir}, '--all',
'-m', 'resolve conflicts: ' . $message, '--author', $author, $file) == 0 ||
error("'darcs record' failed");
my $conflicts = darcs_rev($file) ne $rcstoken;
unlink("$config{srcdir}/$file.log") or
error("failed to remove '$file.log'");
# Push the changes to the main repository.
silentsystem('darcs', 'push', '--quiet', '--repodir', $config{srcdir}, '--all') == 0 ||
error("'darcs push' failed");
# TODO: darcs send?
if ($conflicts) {
my $document = readfile("$config{srcdir}/$file");
# Try to leave everything in a consistent state.
# TODO: 'yes | ...' needed? Doesn't seem so.
silentsystem('darcs', "revert", "--repodir", $config{srcdir}, "--all") == 0 ||
warn("'darcs revert' failed");
return $document;
}
else {
return undef;
}
}
sub rcs_commit_staged($$$) {
my ($message, $user, $ipaddr) = @_;
my $author;
if (defined $user) {
$author = "$user\@web";
}
elsif (defined $ipaddr) {
$author = "$ipaddr\@web";
}
else {
$author = "anon\@web";
}
if (!defined $message || !length($message)) {
$message = "empty message";
}
silentsystem('darcs', "record", "--repodir", $config{srcdir}, "-a", "-A", $author,
"-m", $message) == 0 || error("'darcs record' failed");
# Push the changes to the main repository.
silentsystem('darcs', 'push', '--quiet', '--repodir', $config{srcdir}, '--all') == 0 ||
error("'darcs push' failed");
# TODO: darcs send?
return undef;
}
sub rcs_add ($) {
my $file = shift; # Relative to the repodir.
if(! file_in_vc($config{srcdir}, $file)) {
# Intermediate directories will be added automagically.
system('darcs', 'add', '--quiet', '--repodir', $config{srcdir},
'--boring', $file) == 0 || error("'darcs add' failed");
}
}
sub rcs_remove ($) {
my $file = shift; # Relative to the repodir.
unlink($config{srcdir}.'/'.$file);
}
sub rcs_rename ($$) {
my $a = shift; # Relative to the repodir.
my $b = shift; # Relative to the repodir.
system('darcs', 'mv', '--repodir', $config{srcdir}, $a, $b) == 0 ||
error("'darcs mv' failed");
}
sub rcs_recentchanges ($) {
my $num=shift;
my @ret;
eval q{use Date::Parse};
eval q{use XML::Simple};
my $repodir=$config{srcdir};
debug("darcs recent changes: $num");
my $child = open(LOG, "-|");
if (! $child) {
$ENV{"DARCS_DONT_ESCAPE_ANYTHING"}=1;
exec("darcs", "changes", "--xml",
"--summary",
"--repodir", "$repodir",
"--last", "$num")
|| error("'darcs changes' failed to run");
}
my $data;
$data .= $_ while(<LOG>);
close LOG;
my $log = XMLin($data, ForceArray => 1);
debug("parsing recent changes...");
foreach my $patch (@{$log->{patch}}) {
my $date=$patch->{local_date};
my $hash=$patch->{hash};
my $when=str2time($date);
my (@pages, @files, @pg);
push @pages, $_ for (@{$patch->{summary}->[0]->{modify_file}});
push @pages, $_ for (@{$patch->{summary}->[0]->{add_file}});
push @pages, $_ for (@{$patch->{summary}->[0]->{remove_file}});
foreach my $f (@pages) {
$f = $f->{content} if ref $f;
$f =~ s,^\s+,,; $f =~ s,\s+$,,; # cut whitespace
push @files, $f;
}
foreach my $p (@{$patch->{summary}->[0]->{move}}) {
push @files, $p->{from};
}
foreach my $f (@files) {
my $d = defined $config{'diffurl'} ? $config{'diffurl'} : "";
$d =~ s/\[\[file\]\]/$f/go;
$d =~ s/\[\[hash\]\]/$hash/go;
debug("file: $f");
debug("diffurl: $d");
push @pg, {
page => pagename($f),
diffurl => $d,
};
}
next unless (scalar @pg > 0);
debug("recent change: " . $patch->{name}[0] . " ("
. scalar @pg . " changes)");
my @message;
push @message, { line => $_ } foreach (@{$patch->{name}});
my $committype;
my $author;
if ($patch->{author} =~ /(.*)\@web$/) {
$author = $1;
$committype = "web";
}
else {
$author=$patch->{author};
$committype = "darcs";
}
push @ret, {
rev => $patch->{hash},
user => $author,
committype => $committype,
when => $when,
message => [@message],
pages => [@pg],
};
}
return @ret;
}
sub rcs_diff ($) {
my $rev=shift;
my @lines;
foreach my $line (silentsystem("darcs", "diff", "--match", "hash ".$rev)) {
if (@lines || $line=~/^diff/) {
push @lines, $line."\n";
}
}
if (wantarray) {
return @lines;
}
else {
return join("", @lines);
}
}
sub rcs_getctime ($) {
my $file=shift;
eval q{use Date::Parse};
eval q{use XML::Simple};
local $/=undef;
# Sigh... doing things the hard way again
my $repodir=$config{srcdir};
my $filer=substr($file, length($repodir));
$filer =~ s:^[/]+::;
my $child = open(LOG, "-|");
if (! $child) {
exec("darcs", "changes", "--xml", "--reverse",
"--repodir", "$repodir", "$filer")
|| error("'darcs changes $filer' failed to run");
}
my $data;
{
local $/=undef;
$data = <LOG>;
}
close LOG;
my $log = XMLin($data, ForceArray => 1);
my $datestr = $log->{patch}[0]->{local_date};
if (! defined $datestr) {
warn "failed to get ctime for $filer";
return 0;
}
my $date = str2time($datestr);
debug("ctime for '$file': ". localtime($date));
return $date;
}
1

View File

@ -58,6 +58,9 @@ sub import (@) {
elsif ($config{rcs} eq 'monotone') {
$config{mtn_wrapper}=$config{srcdir}."_MTN/ikiwiki-netsync-hook";
}
elsif ($config{rcs} eq 'darcs') {
$config{darcs_wrapper}=$config{repository}."/_darcs/ikiwiki-wrapper";
}
elsif ($config{rcs} eq 'bzr') {
# TODO
}

10
debian/changelog vendored
View File

@ -1,8 +1,16 @@
ikiwiki (3.10) UNRELEASED; urgency=low
* darcs: Finally added support for this VCS, thanks to many
contributors:
- Thomas Schwinge wrote the original file, implementing only rcs_commit.
- Benjamin A'Lee contributed an alternative implementation.
- Tuomo Valkonen contributed rcs_getctime and stub rcs_recentchanges.
- Simon Michael contributed multiple changes.
- Petr Ročkai fixed rcs_recentchanges.
- Sven M. Hallberg merged the above and added missing features.
* Add missing newline to Confirm Password prompt.
-- Joey Hess <joeyh@debian.org> Sat, 04 Apr 2009 18:25:33 -0400
-- Joey Hess <joeyh@debian.org> Sat, 04 Apr 2009 17:47:36 -0400
ikiwiki (3.09) unstable; urgency=low

10
debian/copyright vendored
View File

@ -32,6 +32,16 @@ Files: tla.pm
Copyright: © 2006 Clint Adams <schizo@debian.org>
License: GPL-2+
Files: darcs.pm
Copyright:
© 2006 Thomas Schwinge <tschwinge@gnu.org>
2007 Benjamin A'Lee <bma@bmalee.eu>
Tuomo Valkonen <tuomov@iki.fi>
2008 Simon Michael <simon@joyful.com>
Petr Ročkai <me@mornfall.net>
Sven M. Hallberg <pesco@khjk.org>
License: GPL-2+
Files: teximg.pm
Copyright: © 2007 Patrick Winnertz <patrick.winnertz@skolelinux.org>
License: GPL-2+

View File

@ -4,7 +4,7 @@ ikiwiki-makerepo - check an ikiwiki srcdir into revision control
# SYNOPSIS
ikiwiki-makerepo svn|git|monotone srcdir repository
ikiwiki-makerepo svn|git|monotone|darcs srcdir repository
ikiwiki-makerepo bzr|mercurial srcdir
@ -18,6 +18,9 @@ Note that for mercurial and bzr, the srcdir is converted into a
repository. There is no need to have a separate repository with mercurial
or bzr.
For darcs, the master repo's apply hook will be preconfigured to call a
ikiwiki wrapper.
Note that for monotone, you are assumed to already have run "mtn genkey"
to generate a key.

15
doc/rcs/darcs.mdwn 100644
View File

@ -0,0 +1,15 @@
[Darcs](http://darcs.new) is a distributed revison control
system. Ikiwiki supports storing a wiki in a
Darcs repository.
An Ikiwiki wrapper is run by the `posthook` to update a wiki whenever commits
or remote pushes come in. When running as a [[cgi]] with Darcs, ikiwiki
automatically commits edited pages, and uses the Darcs history to generate the
[[RecentChanges]] page.
Example for a `_darcs/prefs/defaults` file in `$SRCDIR`:
apply posthook /path/to/repository/_darcs/ikiwiki-wrapper
apply run-posthook
See also [[todo/darcs|todo/darcs]]

View File

@ -32,98 +32,20 @@ You browse and web-edit the wiki on W.
W "belongs" to ikiwiki and should not be edited directly.
## [darcs](http://darcs.net/) (not yet included)
## [[darcs]]
Support for using darcs as a backend is being worked on by [Thomas
Schwinge](mailto:tschwinge@gnu.org), although development is on hold curretly.
There is a patch in [[todo/darcs]].
Regarding the repository layout: There are two darcs repositories. One is the `srcdir`, the other we'll call `master`.
### How will it work internally?
* HTML is generated from `srcdir`.
* CGI edits happen in `srcdir`.
* The backend pulls updates from `master` into `srcdir`, i.e. darcs commits should happen to `master`.
* `master` calls ikiwiki (through a wrapper) in its apply posthook, i.e. `master/_darcs/prefs/defaults` should look like this:
``Master'' repository R1.
RCS commits from the outside are installed into R1.
HTML is generated from R1. HTML is automatically generated (by using a
``post-hook'') each time a new change is installed into R1. It follows
that rcs_update() is not needed.
There is a working copy of R1: R2.
CGI operates on R2. rcs_commit() will push from R2 to R1.
You browse the wiki on R1 and web-edit it on R2. This means for example
that R2 needs to be updated from R1 if you are going to web-edit a page,
as the user otherwise might be irritated otherwise...
How do changes get from R1 to R2? Currently only internally in
rcs\_commit(). Is rcs\_prepedit() suitable?
It follows that the HTML rendering and the CGI handling can be completely
separated parts in ikiwiki.
What repository should [[RecentChanges]] and History work on? R1?
#### Rationale for doing it differently than in the Subversion case
darcs is a distributed RCS, which means that every checkout of a
repository is equal to the repository it was checked-out from. There is
no forced hierarchy.
R1 is nevertheless called the master repository. It's used for
collecting all the changes and publishing them: on the one hand via the
rendered HTML and on the other via the standard darcs RCS interface.
R2, the repository the CGI operates on, is just a checkout of R1 and
doesn't really differ from the other checkouts that people will branch
off from R1.
(To be continued.)
#### Another possible approach
Here's what I (tuomov) think, would be a “cleaner” approach:
1. Upon starting to edit, Ikiwiki gets a copy of the page, and `darcs changes --context`.
This context _and_ the present version of the page are stored in as the “version” of the
page in a hidden control of the HTML.
Thus the HTML includes all that is needed to generate a patch wrt. to the state of the
repository at the time the edit was started. This is of course all that darcs needs.
2. Once the user is done with editing, _Ikiwiki generates a patch bundle_ for darcs.
This should be easy with existing `Text::Diff` or somesuch modules, as the Web edits
only concern single files. The reason why the old version of the page is stored in
the HTML (possibly compressed) is that the diff can be generated.
3. Now this patch bundle is applied with `darcs apply`, or sent by email for moderation…
there are many possibilities.
This approach avoids some of the problems of concurrent edits that the previous one may have,
although there may be conflicts, which may or may not propagate to the displayed web page.
(Unfortunately there is not an option to `darcs apply` to generate some sort of confliction resolution
bundle.) Also, only one repository is needed, as it is never directly modified
by Ikiwiki.
This approach might be applicable to other distributed VCSs as well, although they're not as oriented
towards transmitting changes with standalone patch bundles (often by email) as darcs is.
> The mercurial plugin seems to just use one repo and edit it directly - is
> there some reason that's okay there but not for darcs? I agree with tuomov
> that having just the one repo would be preferable; the point of a dvcs is
> that there's no difference between one repo and another. I've got a
> darcs.pm based on mercurial.pm, that's almost usable... --bma
>> IMHO it comes down to whatever works well for a given RCS. Seems like
>> the darcs approach _could_ be done with most any distributed system, but
>> it might be overkill for some (or all?) While there is the incomplete darcs
>> plugin in [[todo/darcs]], if you submit one that's complete, I will
>> probably accept it into ikiwiki.. --[[Joey]]
>>> I'd like to help make a robust darcs (2) backend. I also think ikiwiki should use
>>> exactly one darcs repo. I think we can simplify and say conflicting web
>>> edits are not allowed, like most current wiki engines. I don't see that
>>> saving (so much) context in the html is necessary, then.
>>> bma, I would like to see your code. --[[Simon_Michael]]
>>> PS ah, there it is. Let's continue on the [[todo/darcs]] page.
apply posthook ikiwrap
apply run-posthook
* The backend pushes CGI edits from `srcdir` back into `master` (triggering the apply hook).
* The working copies in `srcdir` and `master` should *not* be touched by the user, only by the CGI or darcs, respectively.
## [[Git]]
@ -319,6 +241,8 @@ please refer to [Emanuele](http://nerd.ocracy.org/em/)
## [[tla]]
Nobody really understands how tla works. ;-)
## rcs
There is a patch that needs a bit of work linked to from [[todo/rcs]].

View File

@ -1,529 +1,21 @@
Here's Thomas Schwinge unfinished darcs support for ikiwiki.
(Finishing this has been suggested as a [[soc]] project.)
> I haven't been working on this for months and also won't in the near
> future. Feel free to use what I have done so
> far and bring it into an usable state! Also, feel free to contact me
> if there are questions.
-- [Thomas Schwinge](mailto:tschwinge@gnu.org)
[[!toggle text="show"]]
[[!toggleable text="""
# Support for the darcs rcs, <URL:http://darcs.net/>.
# Copyright (C) 2006 Thomas Schwinge <tschwinge@gnu.org>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
# We're guaranteed to be the only instance of ikiwiki running at a given
# time. It is essential that only ikiwiki is working on a particular
# repository. That means one instance of ikiwiki and it also means that
# you must not `darcs push' into this repository, as this might create
# race conditions, as I understand it.
use warnings;
use strict;
use IkiWiki;
package IkiWiki;
# Which darcs executable to use.
my $darcs = ($ENV{DARCS} or 'darcs');
# Internal functions.
sub darcs_info ($$$) {
my $field = shift;
my $repodir = shift;
my $file = shift; # Relative to the repodir.
my $child = open(DARCS_CHANGES, "-|");
if (! $child) {
exec($darcs, 'changes', '--repo=' . $repodir, '--xml-output', $file) or
error('failed to run `darcs changes\'');
}
# Brute force for now. :-/
while (<DARCS_CHANGES>) {
last if /^<\/created_as>$/;
}
($_) = <DARCS_CHANGES> =~ /$field=\'([^\']+)/;
$field eq 'hash' and s/\.gz//; # Strip away the `.gz' from `hash'es.
close(DARCS_CHANGES) or error('`darcs changes\' exited ' . $?);
return $_;
}
# Exported functions.
sub rcs_update () {
# Not needed.
}
sub rcs_prepedit ($) {
# Prepares to edit a file under revision control. Returns a token that
# must be passed to rcs_commit() when the file is to be commited. For us,
# this token the hash value of the latest patch that modifies the file,
# i.e. something like its current revision. If the file is not yet added
# to the repository, we return TODO: the empty string.
my $file = shift; # Relative to the repodir.
my $hash = darcs_info('hash', $config{srcdir}, $file);
return defined $hash ? $hash : "";
}
sub rcs_commit ($$$) {
# Commit the page. Returns `undef' on success and a version of the page
# with conflict markers on failure.
my $file = shift; # Relative to the repodir.
my $message = shift;
my $rcstoken = shift;
# Compute if the ``revision'' of $file changed.
my $changed = darcs_info('hash', $config{srcdir}, $file) ne $rcstoken;
# Yes, the following is a bit convoluted.
if ($changed) {
# TODO. Invent a better, non-conflicting name.
rename("$config{srcdir}/$file", "$config{srcdir}/$file.save") or
error("failed to rename $file to $file.save: $!");
# Roll the repository back to $rcstoken.
# TODO. Can we be sure that no changes are lost? I think that
# we can, if we make sure that the `darcs push' below will always
# succeed.
# We need to revert everything as `darcs obliterate' might choke
# otherwise.
# TODO: `yes | ...' needed? Doesn't seem so.
system($darcs, "revert", "--repodir=" . $config{srcdir}, "--all") and
error("`darcs revert' failed");
# Remove all patches starting at $rcstoken.
# TODO. Something like `yes | darcs obliterate ...' seems to be needed.
system($darcs, "obliterate", "--quiet", "--repodir" . $config{srcdir},
"--match", "hash " . $rcstoken) and
error("`darcs obliterate' failed");
# Restore the $rcstoken one.
system($darcs, "pull", "--quiet", "--repodir=" . $config{srcdir},
"--match", "hash " . $rcstoken, "--all") and
error("`darcs pull' failed");
# We're back at $rcstoken. Re-install the modified file.
rename("$config{srcdir}/$file.save", "$config{srcdir}/$file") or
error("failed to rename $file.save to $file: $!");
}
# Record the changes.
# TODO: What if $message is empty?
writefile("$file.log", $config{srcdir}, $message);
system($darcs, 'record', '--repodir=' . $config{srcdir}, '--all',
'--logfile=' . "$config{srcdir}/$file.log",
'--author=' . 'web commit <web-hurd@gnu.org>', $file) and
error('`darcs record\' failed');
# Update the repository by pulling from the default repository, which is
# master repository.
system($darcs, "pull", "--quiet", "--repodir=" . $config{srcdir},
"--all") and error("`darcs pull' failed\n");
# If this updating yields any conflicts, we'll record them now to resolve
# them. If nothing is recorded, there are no conflicts.
$rcstoken = darcs_info('hash', $config{srcdir}, $file);
# TODO: Use only the first line here, i.e. only the patch name?
writefile("$file.log", $config{srcdir}, 'resolve conflicts: ' . $message);
system($darcs, 'record', '--repodir=' . $config{srcdir}, '--all',
'--logfile=' . "$config{srcdir}/$file.log",
'--author=' . 'web commit <web-hurd@gnu.org>', $file) and
error('`darcs record\' failed');
my $conflicts = darcs_info('hash', $config{srcdir}, $file) ne $rcstoken;
unlink("$config{srcdir}/$file.log") or
error("failed to remove `$file.log'");
# Push the changes to the main repository.
system($darcs, 'push', '--quiet', '--repodir=' . $config{srcdir}, '--all')
and error('`darcs push\' failed');
# TODO: darcs send?
if ($conflicts) {
my $document = readfile("$config{srcdir}/$file");
# Try to leave everything in a consistent state.
# TODO: `yes | ...' needed? Doesn't seem so.
system($darcs, "revert", "--repodir=" . $config{srcdir}, "--all") and
warn("`darcs revert' failed.\n");
return $document;
} else {
return undef;
}
}
sub rcs_add ($) {
my $file = shift; # Relative to the repodir.
# Intermediate directories will be added automagically.
system($darcs, 'add', '--quiet', '--repodir=' . $config{srcdir},
'--boring', $file) and error('`darcs add\' failed');
}
sub rcs_recentchanges ($) {
warn('rcs_recentchanges() is not implemented');
return 'rcs_recentchanges() is not implemented';
}
sub rcs_notify () {
warn('rcs_notify() is not implemented');
}
sub rcs_getctime () {
warn('rcs_getctime() is not implemented');
}
1
"""]]
This is my ([bma](bma@bmalee.eu)) darcs.pm - it's messy (my Perl isn't up to much) but seems to work. It uses just one repo, like the mercurial plugin (unlike the above version, which AIUI uses two).
`rcs_commit()` uses backticks instead of `system()`, to prevent darcs' output being sent to the browser and mucking with the HTTP headers (`darcs record` has no --quiet option). And `rcs_recentchanges()` uses regexes rather than parsing darcs' XML output.
[[!toggle text="show" id="bma"]]
[[!toggleable id="bma" text="""
#!/usr/bin/perl
use warnings;
use strict;
use IkiWiki;
use Date::Parse;
use open qw{:utf8 :std};
package IkiWiki;
sub rcs_update () {
# Do nothing - there's nowhere to update *from*.
}
sub rcs_prepedit ($) {
}
sub rcs_commit ($$$;$$) {
my ($file, $message, $rcstoken, $user, $ipaddr) = @_;
# $user should probably be a name and an email address, by darcs
# convention.
if (defined $user) {
$user = possibly_foolish_untaint($user);
}
elsif (defined $ipaddr) {
$user = "Anonymous from $ipaddr";
}
else {
$user = "Anonymous";
}
$message = possibly_foolish_untaint($message);
# BUG: this outputs one line of text, and there's not a -q or --quiet
# option. Redirecting output to /dev/null works, but I still get the
# HTTP status and location headers displayed in the browser - is that
# darcs' fault or ikiwiki's?
# Doing it in backticks *works*, but I'm sure it could be done better.
my @cmdline = ("darcs", "record", "--repodir", "$config{srcdir}",
"-a", "-m", "$message", "--author", "$user", $file);
`darcs record --repodir "$config{srcdir}" -a -m "$message" --author "$user" $file`; # Return value? Output? Who needs 'em?
#if (system(@cmdline) != 0) {
# warn "'@cmdline' failed: $!";
#}
return undef; # success
sub rcs_add ($) {
my ($file) = @_;
my @cmdline = ("darcs", "add", "--repodir", "$config{srcdir}", "-a", "-q", "$file");
if (system(@cmdline) != 0) {
warn "'@cmdline' failed: $!";
}
}
sub rcs_recentchanges ($) {
# TODO: This is horrible code. It doesn't work perfectly, and uses regexes
# rather than parsing Darcs' XML output.
my $num=shift;
my @ret;
return unless -d "$config{srcdir}/_darcs";
my $changelog = `darcs changes --xml --summary --repodir "$config{srcdir}"`;
$changelog = join("", split(/\s*\n\s*/, $changelog));
my @changes = split(/<\/patch>.*?<patch/m, $changelog);
foreach my $change (@changes) {
$change =~ m/hash='(.*?)'/;
my $rev = $1;
$change =~ m/author='(.*?)'/;
my $user = $1."\n";
my $committype = "web";
if($user =~ m/&lt;/) {
# Author fields generated by darcs include an email address: look for the "<".
$committype = "darcs";
use HTML::Entities;
$user = decode_entities $user;
}
$change =~ m/local_date='(.*?)'/;
my $when = $1;
$when=time - str2time($when, 'UTC');
$change =~ m/<name>(.*?)<\/name>/g;
my @message = {line => $1};
foreach my $match ($change =~ m/<comment>(.*?)<\/comment>/gm) {
push @message, {line => $1};
}
my @pages;
foreach my $match ($change =~ m/<.*?_(file|directory)>(.*?)(<(added|removed)_lines.*\/>)*<\/.*?_(file|directory)>/g) {
# My perl-fu is weak. I'm probably going about this all wrong, anyway.
push @pages, {page => pagename($match)} if ( -f $config{srcdir}."/".$match || -d $config{srcdir}."/".$match) and not $match =~ m/^$/;
}
push @ret, { rev => $rev,
user => $user,
committype => $committype,
when => $when,
message => [@message],
pages => [@pages],
}
}
return @ret;
}
sub rcs_notify () {
# TODO
}
sub rcs_getctime ($) {
error gettext("getctime not implemented");
}
1
"""]]
---
Well, here's my version too. It only does getctime -- using a real XML parser, instead of regexp ugliness -- and maybe recentchanges, but that may be bitrotted, or maybe I never finished it, as I only need the getctime. As for actual commits, I have previously voiced my opinion, that this should be done by the plugin generating a patch bundle, and forwarding it to darcs in some way (`darcs apply` or even email to another host, possibly moderated), instead of the hacky direct modification of a working copy. It could also be faster to getctime in a batch. Just reading in all the changes the first time they're needed, might not be a big improvement in many cases, but if we got a batch request from ikiwiki, we could keep reaing the changes until all the files in this batch request have been met. --[[tuomov]]
[[!toggle text="show" id="tuomov"]]
[[!toggleable id="tuomov" text="""
<pre>
#!/usr/bin/perl
# Stubs for no revision control.
use warnings;
use strict;
use IkiWiki;
package IkiWiki;
sub rcs_update () {
}
sub rcs_prepedit ($) {
return ""
}
sub rcs_commit ($$$) {
return undef # success
}
sub rcs_add ($) {
}
sub rcs_recentchanges ($) {
my $num=shift;
my @ret;
eval q{use Date::Parse};
eval q{use XML::Simple};
my $repodir=$config{srcdir};
if (-d "$config{srcdir}/_darcs") {
my $child = open(LOG, "-|");
if (! $child) {
exec("darcs", "changes", "--xml",
"--repodir", "$repodir",
"--last", "$num")
|| error("darcs changes failed to run");
}
my $data=<LOG>;
close LOG;
my $log = XMLin($data, ForceArray => 1);
foreach my $patch ($log->{patch}) {
my $date=$patch->{local_date};
my $hash=$patch->{hash};
my $when=concise(ago(time - str2time($date)));
my @pages;
my $child = open(SUMMARY, "-|");
if (! $child) {
exec("darcs", "annotate", "-s", "--xml",
"--match", "hash: $hash",
"--repodir", "$repodir")
|| error("darcs annotate failed to run");
}
my $data=<SUMMARY>;
close SUMMARY;
my $summary = XMLin("<lame>$data</lame>", ForceArray => 1);
# TODO: find @pages
push @ret, {
#rev => $rev,
user => $patch->{author},
#committype => $committype,
when => $when,
#message => [@message],
pages => [@pages],
}; # if @pages;
return @ret if @ret >= $num;
}
}
return @ret;
}
sub rcs_notify () {
}
sub rcs_getctime ($) {
my $file=shift;
eval q{use Date::Parse};
eval q{use XML::Simple};
local $/=undef;
# Sigh... doing things the hard way again
my $repodir=$config{srcdir};
my $filer=substr($file, length($repodir));
$filer =~ s:^[/]+::;
my $child = open(LOG, "-|");
if (! $child) {
exec("darcs", "changes", "--xml", "--reverse",
"--repodir", "$repodir", "$filer")
|| error("darcs changes $filer failed to run");
}
my $data=<LOG>;
close LOG;
my $log = XMLin($data, ForceArray => 1);
my $datestr=$log->{patch}[0]->{local_date};
if (! defined $datestr) {
warn "failed to get ctime for $filer";
return 0;
}
my $date=str2time($datestr);
debug("found ctime ".localtime($date)." for $file");
return $date;
}
1
</pre>
"""]]
---
I merged the two versions above and made some fixes; it is recording my web edits in darcs and showing a recent changes page.
It is in a [darcs repository](http://joyful.com/darcsweb/darcsweb.cgi?r=ikiwiki-darcs), please send patches. --[[Simon_Michael]]
> I'd like to see at least the following fixed before I commit this: --[[Joey]]
> * Running `darcs record $filename` in backticks is not good (security)
> The thing to do is to open stdout to /dev/null before execing darcs.
> * Get `rcs_recentchanges_xml` working, parsing xml with regexps does
> not seem like a maintenance win.
> * `rcs_notify` should be removed, it's no longer used.
> * Some form of conflict handling. Using darcs to attempt to merge
> the changes is I gusss optional (although every other rcs backend,
> including svn manages to do this), but it needs to at *least* detect
> conflicts and return a page with conflict markers for the user to fix
> the conflict.
I have addressed the recentchanges bit, you can find my hacked up darcs.pm at <http://web.mornfall.net/stuff/web-root/IkiWiki/Rcs/darcs.pm>.
It's got couple of FIXMEs, and a very site-specific filter for recentchanges. Not sure how to do that better though. I will eventually add web commits, probably of my own (and mention it here).
---
And here's yet another one, including an updated `ikiwiki-makerepo`. :)
<http://khjk.org/~pesco/ikiwiki-darcs/> (now a darcs repo)
> Note that there's a 'darcs' branch in git that I'm keeping a copy of your
> code in. Just in case. :-)
I've taken all the good stuff from the above and added the missing hooks. The code hasn't seen a lot of testing, so some bugs are likely yet to surface. Also, I'm not experienced with perl and don't know where I should have used the function `possibly_foolish_untaint`.
Regarding the repository layout: There are two darcs repositories. One is the `srcdir`, the other we'll call `master`.
* HTML is generated from `srcdir`.
* CGI edits happen in `srcdir`.
* The backend pulls updates from `master` into `srcdir`, i.e. darcs commits should happen to `master`.
* `master` calls ikiwiki (through a wrapper) in its apply posthook, i.e. `master/_darcs/prefs/defaults` should look like this:
apply posthook ikiwrap
apply run-posthook
(I'm not sure, should/could it be `ikiwrap --refresh` above?)
* The backend pushes CGI edits from `srcdir` back into `master` (triggering the apply hook).
* The working copies in `srcdir` and `master` should *not* be touched by the user, only by the CGI or darcs, respectively.
I've taken all the good stuff from the above (now deleted --[[Joey]]) and added the missing hooks. The code hasn't seen a lot of testing, so some bugs are likely yet to surface. Also, I'm not experienced with perl and don't know where I should have used the function `possibly_foolish_untaint`.
> Review of this one:
>
> * Should use tab indentation.
> * Should use tab indentation. (fixed)
> * `rcs_getctime` should not need to use a ctime cache (such a cache should
> also not be named `.ikiwiki.ctimes`). `rcs_getctime` is run exactly
> once per page, ever, and the data is cached in ikiwiki's index.
> once per page, ever, and the data is cached in ikiwiki's index. (fixed)
> * I doubt that ENV{DARCS} will be available, since the wrapper clobbers> the entire
> environment. I'd say remove that.
> environment. I'd say remove that. (fixed)
> * I don't understand what `darcs_info` is doing, but it seems to be
> parsing xml with a regexp?
> * Looks like `rcs_commit` needs a few improvements, as marked TODO
> * `rcs_remove` just calls "rm"? Does darcs record notice the file was removed
> and automatically commit the removal? (And why `system("rm")` and not
> `unlink`?)
> * Is the the darcs info in [[details]] still up-to-date re this version?
> * `rcs_remove` just calls unlink? Does darcs record notice the file was removed
> and automatically commit the removal?
> * Is the the darcs info in [[rcs/details]] still up-to-date re this version? (fixed)
> --[[Joey]]
> Update:
@ -537,6 +29,8 @@ Regarding the repository layout: There are two darcs repositories. One is the `s
> this version works. It's similar, but the details differ slightly.
> You could copy my description above to replace it.
>
>> done --[[Joey]]
>
> There is still some ironing to do, for instance the current version doesn't allow for
> modifying attachments by re-uploading them via CGI ("darcs add failed"). Am I assuming
> correctly that "adding" a file that's already in the repo should just be a no-op?
@ -548,4 +42,12 @@ Regarding the repository layout: There are two darcs repositories. One is the `s
>>> Done. --pesco
[[!tag patch]]
----
I've finally merged this into ikiwiki master. The plugin looks quite
complete, with only the new `rcs_receive` hook missing, and I
hope it works as good as it looks. :) If anyone wants to work on improving
it, there are some TODOs as mentioned above that could still be improved.
--[[Joey]]
[[!tag patch done]]

View File

@ -6,7 +6,7 @@ srcdir="$2"
repository="$3"
usage () {
echo "usage: ikiwiki-makerepo svn|git|monotone srcdir repository" >&2
echo "usage: ikiwiki-makerepo svn|git|monotone|darcs srcdir repository" >&2
echo " ikiwiki-makerepo bzr|mercurial srcdir" >&2
exit 1
}
@ -121,6 +121,31 @@ monotone)
echo ' return "passphrasehere"'
echo "end"
;;
darcs)
if [ -e "$srcdir/_darcs" ]; then
echo "$srcdir already seems to be a darcs repository" >&2
exit 1
fi
mkdir -p "$repository"
cd "$repository"
darcs initialize
mkdir -p "$srcdir"
cd "$srcdir"
darcs initialize
echo .ikiwiki >> _darcs/prefs/boring
darcs record -a -l -q -m "initial import"
darcs pull -a -q "$repository"
darcs push -a -q "$repository"
echo "Directory $srcdir is now a branch of darcs repo $repository"
# set up master repo's apply hook and tell user to adjust it if desired
darcsdefaults="$repository/_darcs/prefs/defaults"
echo "Preconfiguring apply hook in $darcsdefaults - adjust as desired!"
echo "apply posthook $repository/_darcs/ikiwiki-wrapper" >> "$darcsdefaults"
echo "apply run-posthook" >> "$darcsdefaults"
;;
*)
echo "Unsupported revision control system $rcs" >&2
usage

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-04-04 14:59-0400\n"
"POT-Creation-Date: 2009-04-04 18:19-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"
@ -951,12 +951,12 @@ msgstr ""
msgid "you must enter a wikiname (that contains alphanumerics)"
msgstr ""
#: ../IkiWiki/Setup/Automator.pm:68
#: ../IkiWiki/Setup/Automator.pm:71
#, perl-format
msgid "unsupported revision control system %s"
msgstr ""
#: ../IkiWiki/Setup/Automator.pm:94
#: ../IkiWiki/Setup/Automator.pm:97
msgid "failed to set up the repository with ikiwiki-makerepo"
msgstr ""