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);
25 sub checkconfig () { #{{{
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"),
34 sub getsetup () { #{{{
38 #example => "", # FIXME add example
39 description => "bzr post-commit executable to generate",
46 description => "mode for bzr_wrapper (can safely be made suid)",
52 #example => "", # FIXME add example
53 description => "url to show file history, using loggerhead ([[file]] substituted)",
59 example => "http://example.com/revision?start_revid=[[r2]]#[[file]]-s",
60 description => "url to view a diff, using loggerhead ([[file]] and [[r2]] substituted)",
66 sub bzr_log ($) { #{{{
74 if ($line =~ /^message:/) {
76 $infos[$#infos]{$key} = "";
78 elsif ($line =~ /^(modified|added|renamed|renamed and modified|removed):/) {
80 unless (defined($infos[$#infos]{$key})) { $infos[$#infos]{$key} = ""; }
82 elsif (defined($key) and $line =~ /^ (.*)/) {
83 $infos[$#infos]{$key} .= "$1\n";
85 elsif ($line eq "------------------------------------------------------------\n") {
91 ($key, $value) = split /: +/, $line, 2;
92 $infos[$#infos]{$key} = $value;
100 sub rcs_update () { #{{{
101 my @cmdline = ("bzr", "update", "--quiet", $config{srcdir});
102 if (system(@cmdline) != 0) {
103 warn "'@cmdline' failed: $!";
107 sub rcs_prepedit ($) { #{{{
111 sub bzr_author ($$) { #{{{
112 my ($user, $ipaddr) = @_;
115 return IkiWiki::possibly_foolish_untaint($user);
117 elsif (defined $ipaddr) {
118 return "Anonymous from ".IkiWiki::possibly_foolish_untaint($ipaddr);
125 sub rcs_commit ($$$;$$) { #{{{
126 my ($file, $message, $rcstoken, $user, $ipaddr) = @_;
128 $user = bzr_author($user, $ipaddr);
130 $message = IkiWiki::possibly_foolish_untaint($message);
131 if (! length $message) {
132 $message = "no message given";
135 my @cmdline = ("bzr", "commit", "--quiet", "-m", $message, "--author", $user,
136 $config{srcdir}."/".$file);
137 if (system(@cmdline) != 0) {
138 warn "'@cmdline' failed: $!";
141 return undef; # success
144 sub rcs_commit_staged ($$$) {
145 # Commits all staged changes. Changes can be staged using rcs_add,
146 # rcs_remove, and rcs_rename.
147 my ($message, $user, $ipaddr)=@_;
149 $user = bzr_author($user, $ipaddr);
151 $message = IkiWiki::possibly_foolish_untaint($message);
152 if (! length $message) {
153 $message = "no message given";
156 my @cmdline = ("bzr", "commit", "--quiet", "-m", $message, "--author", $user,
158 if (system(@cmdline) != 0) {
159 warn "'@cmdline' failed: $!";
162 return undef; # success
165 sub rcs_add ($) { # {{{
168 my @cmdline = ("bzr", "add", "--quiet", "$config{srcdir}/$file");
169 if (system(@cmdline) != 0) {
170 warn "'@cmdline' failed: $!";
174 sub rcs_remove ($) { # {{{
177 my @cmdline = ("bzr", "rm", "--force", "--quiet", "$config{srcdir}/$file");
178 if (system(@cmdline) != 0) {
179 warn "'@cmdline' failed: $!";
183 sub rcs_rename ($$) { # {{{
184 my ($src, $dest) = @_;
186 my $parent = IkiWiki::dirname($dest);
187 if (system("bzr", "add", "--quiet", "$config{srcdir}/$parent") != 0) {
188 warn("bzr add $parent failed\n");
191 my @cmdline = ("bzr", "mv", "--quiet", "$config{srcdir}/$src", "$config{srcdir}/$dest");
192 if (system(@cmdline) != 0) {
193 warn "'@cmdline' failed: $!";
197 sub rcs_recentchanges ($) { #{{{
200 my @cmdline = ("bzr", "log", "-v", "--show-ids", "--limit", $num,
202 open (my $out, "@cmdline |");
204 eval q{use Date::Parse};
208 foreach my $info (bzr_log($out)) {
212 foreach my $msgline (split(/\n/, $info->{message})) {
213 push @message, { line => $msgline };
216 foreach my $file (split(/\n/, $info->{files})) {
217 my ($filename, $fileid) = ($file =~ /^(.*?) +([^ ]+)$/);
220 next if ($filename =~ /\/$/);
222 # Skip source name in renames
223 $filename =~ s/^.* => //;
225 my $diffurl = defined $config{'diffurl'} ? $config{'diffurl'} : "";
226 $diffurl =~ s/\[\[file\]\]/$filename/go;
227 $diffurl =~ s/\[\[file-id\]\]/$fileid/go;
228 $diffurl =~ s/\[\[r2\]\]/$info->{revno}/go;
231 page => pagename($filename),
236 my $user = $info->{"committer"};
237 if (defined($info->{"author"})) { $user = $info->{"author"}; }
238 $user =~ s/\s*<.*>\s*$//;
242 rev => $info->{"revno"},
245 when => time - str2time($info->{"timestamp"}),
246 message => [@message],
254 sub rcs_getctime ($) { #{{{
257 # XXX filename passes through the shell here, should try to avoid
259 my @cmdline = ("bzr", "log", "--limit", '1', "$config{srcdir}/$file");
260 open (my $out, "@cmdline |");
262 my @log = bzr_log($out);
264 if (length @log < 1) {
268 eval q{use Date::Parse};
271 my $ctime = str2time($log[0]->{"timestamp"});