]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/blobdiff - IkiWiki/Rcs/monotone.pm
fixups
[git.ikiwiki.info.git] / IkiWiki / Rcs / monotone.pm
index 5717e004342875e97259cc09fe22b77f7b981810..d7e8f296a69f6d80c205ebbcaa4e40f3dc49e8a3 100644 (file)
@@ -1,4 +1,7 @@
 #!/usr/bin/perl
+
+package IkiWiki;
+
 use warnings;
 use strict;
 use IkiWiki;
@@ -6,11 +9,9 @@ use Monotone;
 use Date::Parse qw(str2time);
 use Date::Format qw(time2str);
 
-package IkiWiki;
-
 my $sha1_pattern = qr/[0-9a-fA-F]{40}/; # pattern to validate sha1sums
 
-sub check_config() { #{{{
+hook(type => "checkconfig", id => "monotone", call => sub { #{{{
        if (!defined($config{mtnrootdir})) {
                $config{mtnrootdir} = $config{srcdir};
        }
@@ -18,13 +19,87 @@ sub check_config() { #{{{
                error("Ikiwiki srcdir does not seem to be a Monotone workspace (or set the mtnrootdir)!");
        }
        
-       if (!defined($config{mtnmergerc})) {
-               $config{mtnmergerc} = "$config{mtnrootdir}/_MTN/mergerc";
+       my $child = open(MTN, "-|");
+       if (! $child) {
+               open STDERR, ">/dev/null";
+               exec("mtn", "version") || error("mtn version failed to run");
        }
-       
-       chdir $config{srcdir}
-           or error("Cannot chdir to $config{srcdir}: $!");
-} #}}}
+
+       my $version=undef;
+       while (<MTN>) {
+               if (/^monotone (\d+\.\d+) /) {
+                       $version=$1;
+               }
+       }
+
+       close MTN || debug("mtn version exited $?");
+
+       if (!defined($version)) {
+               error("Cannot determine monotone version");
+       }
+       if ($version < 0.38) {
+               error("Monotone version too old, is $version but required 0.38");
+       }
+
+       if (length $config{mtn_wrapper}) {
+               push @{$config{wrappers}}, {
+                       wrapper => $config{mtn_wrapper},
+                       wrappermode => (defined $config{mtn_wrappermode} ? $config{mtn_wrappermode} : "06755"),
+               };
+       }
+}); #}}}
+
+hook(type => "getsetup", id => "monotone", call => sub { #{{{
+       return
+               mtn_wrapper => {
+                       type => "string",
+                       example => "/srv/mtn/wiki/_MTN/ikiwiki-netsync-hook",
+                       description => "monotone netsync hook executable to generate",
+                       safe => 0, # file
+                       rebuild => 0,
+               },
+               mtn_wrappermode => {
+                       type => "string",
+                       example => '06755',
+                       description => "mode for mtn_wrapper (can safely be made suid)",
+                       safe => 0,
+                       rebuild => 0,
+               },
+               mtnkey => {
+                       type => "string",
+                       example => 'web@example.com',
+                       description => "your monotone key",
+                       safe => 1,
+                       rebuild => 0,
+               },
+               historyurl => {
+                       type => "string",
+                       example => "http://viewmtn.example.com/branch/head/filechanges/com.example.branch/[[file]]",
+                       description => "viewmtn url to show file history ([[file]] substituted)",
+                       safe => 1,
+                       rebuild => 1,
+               },
+               diffurl => {
+                       type => "string",
+                       example => "http://viewmtn.example.com/revision/diff/[[r1]]/with/[[r2]]/[[file]]",
+                       description => "viewmtn url to show a diff ([[r1]], [[r2]], and [[file]] substituted)",
+                       safe => 1,
+                       rebuild => 1,
+               },
+               mtnsync => {
+                       type => "boolean",
+                       example => 0,
+                       description => "sync on update and commit?",
+                       safe => 0, # paranoia
+                       rebuild => 0,
+               },
+               mtnrootdir => {
+                       type => "string",
+                       description => "path to your workspace (defaults to the srcdir; specify if the srcdir is a subdirectory of the workspace)",
+                       safe => 0, # path
+                       rebuild => 0,
+               },
+}); #}}}
 
 sub get_rev () { #{{{
        my $sha1 = `mtn --root=$config{mtnrootdir} automate get_base_revision_id`;
@@ -59,13 +134,11 @@ sub mtn_merge ($$$$) { #{{{
     
        my $mergeRev;
 
-       my $mergerc = $config{mtnmergerc};
-    
        my $child = open(MTNMERGE, "-|");
        if (! $child) {
                open STDERR, ">&STDOUT";
-               exec("mtn", "--root=$config{mtnrootdir}", "--rcfile",
-                    $mergerc, "explicit_merge", $leftRev, $rightRev,
+               exec("mtn", "--root=$config{mtnrootdir}",
+                    "explicit_merge", $leftRev, $rightRev,
                     $branch, "--author", $author, "--key", 
                     $config{mtnkey}) || error("mtn merge failed to run");
        }
@@ -130,16 +203,6 @@ sub commit_file_to_new_rev($$$$$$$$) { #{{{
        return $newRevID;
 } #}}}
 
-sub check_mergerc () { #{{{
-       my $mergerc = $config{mtnmergerc};
-       if (! -r $mergerc ) {
-               debug("$mergerc doesn't exist. Creating file with default mergers.");
-               open (my $out, ">", $mergerc) or error("can't open $mergerc: $!");
-               print $out <DATA>;
-               close $out;
-       }
-} #}}}
-
 sub read_certs ($$) { #{{{
        my $automator=shift;
        my $rev=shift;
@@ -183,7 +246,8 @@ sub get_changed_files ($$) { #{{{
 } #}}}
 
 sub rcs_update () { #{{{
-       check_config();
+       chdir $config{srcdir}
+           or error("Cannot chdir to $config{srcdir}: $!");
 
        if (defined($config{mtnsync}) && $config{mtnsync}) {
                if (system("mtn", "--root=$config{mtnrootdir}", "sync",
@@ -201,7 +265,8 @@ sub rcs_update () { #{{{
 sub rcs_prepedit ($) { #{{{
        my $file=shift;
 
-       check_config();
+       chdir $config{srcdir}
+           or error("Cannot chdir to $config{srcdir}: $!");
 
        # For monotone, return the revision of the file when
        # editing begins.
@@ -229,7 +294,8 @@ sub rcs_commit ($$$;$$) { #{{{
                $author="Web: Anonymous";
        }
 
-       check_config();
+       chdir $config{srcdir}
+           or error("Cannot chdir to $config{srcdir}: $!");
 
        my ($oldrev)= $rcstoken=~ m/^($sha1_pattern)$/; # untaint
        my $rev = get_rev();
@@ -275,8 +341,6 @@ sub rcs_commit ($$$;$$) { #{{{
                        }
                        debug("Divergence created! Attempting auto-merge.");
 
-                       check_mergerc();
-
                        # see if it will merge cleanly
                        $ENV{MTN_MERGE}="fail";
                        my $mergeResult = mtn_merge($newRevID, $rev, $branch, $author);
@@ -299,9 +363,11 @@ sub rcs_commit ($$$;$$) { #{{{
                        else {
                                debug("Auto-merge failed.  Using diff-merge to add conflict markers.");
                                
-                               $ENV{MTN_MERGE}="diffutils_force";
+                               $ENV{MTN_MERGE}="diffutils";
+                               $ENV{MTN_MERGE_DIFFUTILS}="partial=true";
                                $mergeResult = mtn_merge($newRevID, $rev, $branch, $author);
                                $ENV{MTN_MERGE}="";
+                               $ENV{MTN_MERGE_DIFFUTILS}="";
                                
                                if (!defined($mergeResult)) {
                                        debug("Unable to insert conflict markers!");
@@ -342,20 +408,51 @@ sub rcs_commit ($$$;$$) { #{{{
                return $conflict;
        }
        if (defined($config{mtnsync}) && $config{mtnsync}) {
-               if (system("mtn", "--root=$config{mtnrootdir}", "sync",
+               if (system("mtn", "--root=$config{mtnrootdir}", "push",
                           "--quiet", "--ticker=none", "--key",
                           $config{mtnkey}) != 0) {
-                       debug("monotone sync failed");
+                       debug("monotone push failed");
                }
        }
 
        return undef # success
 } #}}}
 
+sub rcs_commit_staged ($$$) {
+       # Commits all staged changes. Changes can be staged using rcs_add,
+       # rcs_remove, and rcs_rename.
+       my ($message, $user, $ipaddr)=@_;
+       
+       # Note - this will also commit any spurious changes that happen to be
+       # lying around in the working copy.  There shouldn't be any, but...
+       
+       chdir $config{srcdir}
+           or error("Cannot chdir to $config{srcdir}: $!");
+
+       my $author;
+
+       if (defined $user) {
+               $author="Web user: " . $user;
+       }
+       elsif (defined $ipaddr) {
+               $author="Web IP: " . $ipaddr;
+       }
+       else {
+               $author="Web: Anonymous";
+       }
+
+       if (system("mtn", "--root=$config{mtnrootdir}", "commit", "--quiet",
+                  "--author", $author, "--key", $config{mtnkey}, "-m",
+                  possibly_foolish_untaint($message)) != 0) {
+               error("Monotone commit failed");
+       }
+}
+
 sub rcs_add ($) { #{{{
        my $file=shift;
 
-       check_config();
+       chdir $config{srcdir}
+           or error("Cannot chdir to $config{srcdir}: $!");
 
        if (system("mtn", "--root=$config{mtnrootdir}", "add", "--quiet",
                   $file) != 0) {
@@ -363,11 +460,43 @@ sub rcs_add ($) { #{{{
        }
 } #}}}
 
+sub rcs_remove ($) { # {{{
+       my $file = shift;
+
+       chdir $config{srcdir}
+           or error("Cannot chdir to $config{srcdir}: $!");
+
+       # Note: it is difficult to undo a remove in Monotone at the moment.
+       # Until this is fixed, it might be better to make 'rm' move things
+       # into an attic, rather than actually remove them.
+       # To resurrect a file, you currently add a new file with the contents
+       # you want it to have.  This loses all connectivity and automated
+       # merging with the 'pre-delete' versions of the file.
+
+       if (system("mtn", "--root=$config{mtnrootdir}", "rm", "--quiet",
+                  $file) != 0) {
+               error("Monotone remove failed");
+       }
+} #}}}
+
+sub rcs_rename ($$) { # {{{
+       my ($src, $dest) = @_;
+
+       chdir $config{srcdir}
+           or error("Cannot chdir to $config{srcdir}: $!");
+
+       if (system("mtn", "--root=$config{mtnrootdir}", "rename", "--quiet",
+                  $src, $dest) != 0) {
+               error("Monotone rename failed");
+       }
+} #}}}
+
 sub rcs_recentchanges ($) { #{{{
        my $num=shift;
        my @ret;
 
-       check_config();
+       chdir $config{srcdir}
+           or error("Cannot chdir to $config{srcdir}: $!");
 
        # use log --brief to get a list of revs, as this
        # gives the results in a nice order
@@ -416,7 +545,7 @@ sub rcs_recentchanges ($) { #{{{
                                                $committype = "monotone";
                                        }
                                } elsif ($cert->{name} eq "date") {
-                                       $when = time - str2time($cert->{value}, 'UTC');
+                                       $when = str2time($cert->{value}, 'UTC');
                                } elsif ($cert->{name} eq "changelog") {
                                        my $messageText = $cert->{value};
                                        # split the changelog into multiple
@@ -431,10 +560,28 @@ sub rcs_recentchanges ($) { #{{{
                my @changed_files = get_changed_files($automator, $rev);
                my $file;
                
+               my ($out, $err) = $automator->call("parents", $rev);
+               my @parents = ($out =~ m/^($sha1_pattern)$/);
+               my $parent = $parents[0];
+
                foreach $file (@changed_files) {
-                       push @pages, {
-                               page => pagename($file),
-                       } if length $file;
+                       next unless length $file;
+                       
+                       if (defined $config{diffurl} and (@parents == 1)) {
+                               my $diffurl=$config{diffurl};
+                               $diffurl=~s/\[\[r1\]\]/$parent/g;
+                               $diffurl=~s/\[\[r2\]\]/$rev/g;
+                               $diffurl=~s/\[\[file\]\]/$file/g;
+                               push @pages, {
+                                       page => pagename($file),
+                                       diffurl => $diffurl,
+                               };
+                       }
+                       else {
+                               push @pages, {
+                                       page => pagename($file),
+                               }
+                       }
                }
                
                push @ret, {
@@ -452,58 +599,35 @@ sub rcs_recentchanges ($) { #{{{
        return @ret;
 } #}}}
 
-sub rcs_notify () { #{{{
-       debug("The monotone rcs_notify function is currently untested. Use at own risk!");
+sub rcs_diff ($) { #{{{
+       my $rev=shift;
+       my ($sha1) = $rev =~ /^($sha1_pattern)$/; # untaint
        
-       if (! exists $ENV{REV}) {
-               error(gettext("REV is not set, not running from mtn post-commit hook, cannot send notifications"));
-       }
-       if ($ENV{REV} !~ m/($sha1_pattern)/) { # sha1 is untainted now
-               error(gettext("REV is not a valid revision identifier, cannot send notifications"));
+       chdir $config{srcdir}
+           or error("Cannot chdir to $config{srcdir}: $!");
+
+       my $child = open(MTNDIFF, "-|");
+       if (! $child) {
+               exec("mtn", "diff", "--root=$config{mtnrootdir}", "-r", "p:".$sha1, "-r", $sha1) || error("mtn diff $sha1 failed to run");
        }
-       my $rev = $1;
-       
-       check_config();
 
-       my $automator = Monotone->new();
-       $automator->open(undef, $config{mtnrootdir});
+       my (@lines) = <MTNDIFF>;
 
-       my $certs = [read_certs($automator, $rev)];
-       my $user;
-       my $message;
-       my $when;
+       close MTNDIFF || debug("mtn diff $sha1 exited $?");
 
-       foreach my $cert (@$certs) {
-               if ($cert->{signature} eq "ok" && $cert->{trust} eq "trusted") {
-                       if ($cert->{name} eq "author") {
-                               $user = $cert->{value};
-                       } elsif ($cert->{name} eq "date") {
-                               $when = $cert->{value};
-                       } elsif ($cert->{name} eq "changelog") {
-                               $message = $cert->{value};
-                       }
-               }
+       if (wantarray) {
+               return @lines;
+       }
+       else {
+               return join("", @lines);
        }
-               
-       my @changed_pages = get_changed_files($automator, $rev);
-       
-       $automator->close();
-       
-       require IkiWiki::UserInfo;
-       send_commit_mails(
-               sub {
-                       return $message;
-               },
-               sub {
-                       `mtn --root=$config{mtnrootdir} au content_diff -r $rev`;
-               },
-               $user, @changed_pages);
 } #}}}
 
 sub rcs_getctime ($) { #{{{
        my $file=shift;
 
-       check_config();
+       chdir $config{srcdir}
+           or error("Cannot chdir to $config{srcdir}: $!");
 
        my $child = open(MTNLOG, "-|");
        if (! $child) {
@@ -552,56 +676,3 @@ sub rcs_getctime ($) { #{{{
 } #}}}
 
 1
-
-# default mergerc content
-__DATA__
-       function local_execute_redirected(stdin, stdout, stderr, path, ...)
-          local pid
-          local ret = -1
-          io.flush();
-          pid = spawn_redirected(stdin, stdout, stderr, path, unpack(arg))
-          if (pid ~= -1) then ret, pid = wait(pid) end
-          return ret
-       end
-       if (not execute_redirected) then -- use standard function if available
-          execute_redirected = local_execute_redirected
-       end
-       if (not mergers.fail) then -- use standard merger if available
-          mergers.fail = {
-             cmd = function (tbl) return false end,
-             available = function () return true end,
-             wanted = function () return true end
-          }
-       end
-       mergers.diffutils_force = {
-          cmd = function (tbl)
-             local ret = execute_redirected(
-                 "",
-                 tbl.outfile,
-                 "",
-                 "diff3",
-                 "--merge",
-                 "--show-overlap",
-                 "--label", string.format("[Yours]",     tbl.left_path ),
-                 "--label", string.format("[Original]",  tbl.anc_path  ),
-                 "--label", string.format("[Theirs]",    tbl.right_path),
-                 tbl.lfile,
-                 tbl.afile,
-                 tbl.rfile
-             )
-             if (ret > 1) then
-                io.write(gettext("Error running GNU diffutils 3-way difference tool 'diff3'"))
-                return false
-             end
-             return tbl.outfile
-          end,
-          available =
-             function ()
-                 return program_exists_in_path("diff3");
-             end,
-          wanted =
-             function ()
-                  return true
-             end
-       }
-EOF