2 package IkiWiki::Plugin::bzr;
8 use open qw{:utf8 :std};
11 hook(type => "checkconfig", id => "bzr", call => \&checkconfig);
12 hook(type => "getsetup", id => "bzr", call => \&getsetup);
13 hook(type => "rcs", id => "rcs_update", call => \&rcs_update);
14 hook(type => "rcs", id => "rcs_prepedit", call => \&rcs_prepedit);
15 hook(type => "rcs", id => "rcs_commit", call => \&rcs_commit);
16 hook(type => "rcs", id => "rcs_commit_staged", call => \&rcs_commit_staged);
17 hook(type => "rcs", id => "rcs_add", call => \&rcs_add);
18 hook(type => "rcs", id => "rcs_remove", call => \&rcs_remove);
19 hook(type => "rcs", id => "rcs_rename", call => \&rcs_rename);
20 hook(type => "rcs", id => "rcs_recentchanges", call => \&rcs_recentchanges);
21 hook(type => "rcs", id => "rcs_diff", call => \&rcs_diff);
22 hook(type => "rcs", id => "rcs_getctime", call => \&rcs_getctime);
26 if (defined $config{bzr_wrapper} && length $config{bzr_wrapper}) {
27 push @{$config{wrappers}}, {
28 wrapper => $config{bzr_wrapper},
29 wrappermode => (defined $config{bzr_wrappermode} ? $config{bzr_wrappermode} : "06755"),
37 safe => 0, # rcs plugin
42 #example => "", # FIXME add example
43 description => "bzr post-commit hook to generate",
50 description => "mode for bzr_wrapper (can safely be made suid)",
56 #example => "", # FIXME add example
57 description => "url to show file history, using loggerhead ([[file]] substituted)",
63 example => "http://example.com/revision?start_revid=[[r2]]#[[file]]-s",
64 description => "url to view a diff, using loggerhead ([[file]] and [[r2]] substituted)",
78 if ($line =~ /^message:/) {
80 $infos[$#infos]{$key} = "";
82 elsif ($line =~ /^(modified|added|renamed|renamed and modified|removed):/) {
84 unless (defined($infos[$#infos]{$key})) { $infos[$#infos]{$key} = ""; }
86 elsif (defined($key) and $line =~ /^ (.*)/) {
87 $infos[$#infos]{$key} .= "$1\n";
89 elsif ($line eq "------------------------------------------------------------\n") {
95 ($key, $value) = split /: +/, $line, 2;
96 $infos[$#infos]{$key} = $value;
105 my @cmdline = ("bzr", "update", "--quiet", $config{srcdir});
106 if (system(@cmdline) != 0) {
107 warn "'@cmdline' failed: $!";
111 sub rcs_prepedit ($) {
115 sub bzr_author ($$) {
116 my ($user, $ipaddr) = @_;
119 return IkiWiki::possibly_foolish_untaint($user);
121 elsif (defined $ipaddr) {
122 return "Anonymous from ".IkiWiki::possibly_foolish_untaint($ipaddr);
129 sub rcs_commit ($$$;$$) {
130 my ($file, $message, $rcstoken, $user, $ipaddr) = @_;
132 $user = bzr_author($user, $ipaddr);
134 $message = IkiWiki::possibly_foolish_untaint($message);
135 if (! length $message) {
136 $message = "no message given";
139 my @cmdline = ("bzr", "commit", "--quiet", "-m", $message, "--author", $user,
140 $config{srcdir}."/".$file);
141 if (system(@cmdline) != 0) {
142 warn "'@cmdline' failed: $!";
145 return undef; # success
148 sub rcs_commit_staged ($$$) {
149 # Commits all staged changes. Changes can be staged using rcs_add,
150 # rcs_remove, and rcs_rename.
151 my ($message, $user, $ipaddr)=@_;
153 $user = bzr_author($user, $ipaddr);
155 $message = IkiWiki::possibly_foolish_untaint($message);
156 if (! length $message) {
157 $message = "no message given";
160 my @cmdline = ("bzr", "commit", "--quiet", "-m", $message, "--author", $user,
162 if (system(@cmdline) != 0) {
163 warn "'@cmdline' failed: $!";
166 return undef; # success
172 my @cmdline = ("bzr", "add", "--quiet", "$config{srcdir}/$file");
173 if (system(@cmdline) != 0) {
174 warn "'@cmdline' failed: $!";
181 my @cmdline = ("bzr", "rm", "--force", "--quiet", "$config{srcdir}/$file");
182 if (system(@cmdline) != 0) {
183 warn "'@cmdline' failed: $!";
187 sub rcs_rename ($$) {
188 my ($src, $dest) = @_;
190 my $parent = IkiWiki::dirname($dest);
191 if (system("bzr", "add", "--quiet", "$config{srcdir}/$parent") != 0) {
192 warn("bzr add $parent failed\n");
195 my @cmdline = ("bzr", "mv", "--quiet", "$config{srcdir}/$src", "$config{srcdir}/$dest");
196 if (system(@cmdline) != 0) {
197 warn "'@cmdline' failed: $!";
201 sub rcs_recentchanges ($) {
204 my @cmdline = ("bzr", "log", "-v", "--show-ids", "--limit", $num,
206 open (my $out, "@cmdline |");
208 eval q{use Date::Parse};
212 foreach my $info (bzr_log($out)) {
216 foreach my $msgline (split(/\n/, $info->{message})) {
217 push @message, { line => $msgline };
220 foreach my $file (split(/\n/, $info->{files})) {
221 my ($filename, $fileid) = ($file =~ /^(.*?) +([^ ]+)$/);
224 next if ($filename =~ /\/$/);
226 # Skip source name in renames
227 $filename =~ s/^.* => //;
229 my $diffurl = defined $config{'diffurl'} ? $config{'diffurl'} : "";
230 $diffurl =~ s/\[\[file\]\]/$filename/go;
231 $diffurl =~ s/\[\[file-id\]\]/$fileid/go;
232 $diffurl =~ s/\[\[r2\]\]/$info->{revno}/go;
235 page => pagename($filename),
240 my $user = $info->{"committer"};
241 if (defined($info->{"author"})) { $user = $info->{"author"}; }
242 $user =~ s/\s*<.*>\s*$//;
246 rev => $info->{"revno"},
249 when => str2time($info->{"timestamp"}),
250 message => [@message],
259 my $taintedrev=shift;
260 my ($rev) = $taintedrev =~ /^(\d+(\.\d+)*)$/; # untaint
262 my $prevspec = "before:" . $rev;
263 my $revspec = "revno:" . $rev;
264 my @cmdline = ("bzr", "diff", "--old", $config{srcdir},
265 "--new", $config{srcdir},
266 "-r", $prevspec . ".." . $revspec);
267 open (my $out, "@cmdline |");
274 return join("", @lines);
278 sub rcs_getctime ($) {
281 # XXX filename passes through the shell here, should try to avoid
283 my @cmdline = ("bzr", "log", "--limit", '1', "$config{srcdir}/$file");
284 open (my $out, "@cmdline |");
286 my @log = bzr_log($out);
288 if (length @log < 1) {
292 eval q{use Date::Parse};
295 my $ctime = str2time($log[0]->{"timestamp"});