2 package IkiWiki::Plugin::bzr;
8 use open qw{:utf8 :std};
11 if (exists $IkiWiki::hooks{rcs}) {
12 error(gettext("cannot use multiple rcs plugins"));
14 hook(type => "checkconfig", id => "bzr", call => \&checkconfig);
15 hook(type => "getsetup", id => "bzr", call => \&getsetup);
16 hook(type => "rcs", id => "rcs_update", call => \&rcs_update);
17 hook(type => "rcs", id => "rcs_prepedit", call => \&rcs_prepedit);
18 hook(type => "rcs", id => "rcs_commit", call => \&rcs_commit);
19 hook(type => "rcs", id => "rcs_commit_staged", call => \&rcs_commit_staged);
20 hook(type => "rcs", id => "rcs_add", call => \&rcs_add);
21 hook(type => "rcs", id => "rcs_remove", call => \&rcs_remove);
22 hook(type => "rcs", id => "rcs_rename", call => \&rcs_rename);
23 hook(type => "rcs", id => "rcs_recentchanges", call => \&rcs_recentchanges);
24 hook(type => "rcs", id => "rcs_diff", call => \&rcs_diff);
25 hook(type => "rcs", id => "rcs_getctime", call => \&rcs_getctime);
28 sub checkconfig () { #{{{
29 if (defined $config{bzr_wrapper} && length $config{bzr_wrapper}) {
30 push @{$config{wrappers}}, {
31 wrapper => $config{bzr_wrapper},
32 wrappermode => (defined $config{bzr_wrappermode} ? $config{bzr_wrappermode} : "06755"),
37 sub getsetup () { #{{{
41 #example => "", # FIXME add example
42 description => "bzr post-commit executable to generate",
49 description => "mode for bzr_wrapper (can safely be made suid)",
55 #example => "", # FIXME add example
56 description => "url to show file history, using loggerhead ([[file]] substituted)",
62 example => "http://example.com/revision?start_revid=[[r2]]#[[file]]-s",
63 description => "url to view a diff, using loggerhead ([[file]] and [[r2]] substituted)",
69 sub bzr_log ($) { #{{{
77 if ($line =~ /^message:/) {
79 $infos[$#infos]{$key} = "";
81 elsif ($line =~ /^(modified|added|renamed|renamed and modified|removed):/) {
83 unless (defined($infos[$#infos]{$key})) { $infos[$#infos]{$key} = ""; }
85 elsif (defined($key) and $line =~ /^ (.*)/) {
86 $infos[$#infos]{$key} .= "$1\n";
88 elsif ($line eq "------------------------------------------------------------\n") {
94 ($key, $value) = split /: +/, $line, 2;
95 $infos[$#infos]{$key} = $value;
103 sub rcs_update () { #{{{
104 my @cmdline = ("bzr", "update", "--quiet", $config{srcdir});
105 if (system(@cmdline) != 0) {
106 warn "'@cmdline' failed: $!";
110 sub rcs_prepedit ($) { #{{{
114 sub bzr_author ($$) { #{{{
115 my ($user, $ipaddr) = @_;
118 return IkiWiki::possibly_foolish_untaint($user);
120 elsif (defined $ipaddr) {
121 return "Anonymous from ".IkiWiki::possibly_foolish_untaint($ipaddr);
128 sub rcs_commit ($$$;$$) { #{{{
129 my ($file, $message, $rcstoken, $user, $ipaddr) = @_;
131 $user = bzr_author($user, $ipaddr);
133 $message = IkiWiki::possibly_foolish_untaint($message);
134 if (! length $message) {
135 $message = "no message given";
138 my @cmdline = ("bzr", "commit", "--quiet", "-m", $message, "--author", $user,
139 $config{srcdir}."/".$file);
140 if (system(@cmdline) != 0) {
141 warn "'@cmdline' failed: $!";
144 return undef; # success
147 sub rcs_commit_staged ($$$) {
148 # Commits all staged changes. Changes can be staged using rcs_add,
149 # rcs_remove, and rcs_rename.
150 my ($message, $user, $ipaddr)=@_;
152 $user = bzr_author($user, $ipaddr);
154 $message = IkiWiki::possibly_foolish_untaint($message);
155 if (! length $message) {
156 $message = "no message given";
159 my @cmdline = ("bzr", "commit", "--quiet", "-m", $message, "--author", $user,
161 if (system(@cmdline) != 0) {
162 warn "'@cmdline' failed: $!";
165 return undef; # success
168 sub rcs_add ($) { # {{{
171 my @cmdline = ("bzr", "add", "--quiet", "$config{srcdir}/$file");
172 if (system(@cmdline) != 0) {
173 warn "'@cmdline' failed: $!";
177 sub rcs_remove ($) { # {{{
180 my @cmdline = ("bzr", "rm", "--force", "--quiet", "$config{srcdir}/$file");
181 if (system(@cmdline) != 0) {
182 warn "'@cmdline' failed: $!";
186 sub rcs_rename ($$) { # {{{
187 my ($src, $dest) = @_;
189 my $parent = IkiWiki::dirname($dest);
190 if (system("bzr", "add", "--quiet", "$config{srcdir}/$parent") != 0) {
191 warn("bzr add $parent failed\n");
194 my @cmdline = ("bzr", "mv", "--quiet", "$config{srcdir}/$src", "$config{srcdir}/$dest");
195 if (system(@cmdline) != 0) {
196 warn "'@cmdline' failed: $!";
200 sub rcs_recentchanges ($) { #{{{
203 my @cmdline = ("bzr", "log", "-v", "--show-ids", "--limit", $num,
205 open (my $out, "@cmdline |");
207 eval q{use Date::Parse};
211 foreach my $info (bzr_log($out)) {
215 foreach my $msgline (split(/\n/, $info->{message})) {
216 push @message, { line => $msgline };
219 foreach my $file (split(/\n/, $info->{files})) {
220 my ($filename, $fileid) = ($file =~ /^(.*?) +([^ ]+)$/);
223 next if ($filename =~ /\/$/);
225 # Skip source name in renames
226 $filename =~ s/^.* => //;
228 my $diffurl = defined $config{'diffurl'} ? $config{'diffurl'} : "";
229 $diffurl =~ s/\[\[file\]\]/$filename/go;
230 $diffurl =~ s/\[\[file-id\]\]/$fileid/go;
231 $diffurl =~ s/\[\[r2\]\]/$info->{revno}/go;
234 page => pagename($filename),
239 my $user = $info->{"committer"};
240 if (defined($info->{"author"})) { $user = $info->{"author"}; }
241 $user =~ s/\s*<.*>\s*$//;
245 rev => $info->{"revno"},
248 when => time - str2time($info->{"timestamp"}),
249 message => [@message],
257 sub rcs_getctime ($) { #{{{
260 # XXX filename passes through the shell here, should try to avoid
262 my @cmdline = ("bzr", "log", "--limit", '1', "$config{srcdir}/$file");
263 open (my $out, "@cmdline |");
265 my @log = bzr_log($out);
267 if (length @log < 1) {
271 eval q{use Date::Parse};
274 my $ctime = str2time($log[0]->{"timestamp"});