]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/blobdiff - IkiWiki/Plugin/git.pm
po plugin: initial work
[git.ikiwiki.info.git] / IkiWiki / Plugin / git.pm
index 6c9aca650ee3ab09751ef307aae2febd11a95280..14b0ab2851dc03b561e4c86e9e0c5d660a8ca3a2 100644 (file)
@@ -1,6 +1,5 @@
 #!/usr/bin/perl
 #!/usr/bin/perl
-
-package IkiWiki;
+package IkiWiki::Plugin::git;
 
 use warnings;
 use strict;
 
 use warnings;
 use strict;
@@ -11,30 +10,46 @@ use open qw{:utf8 :std};
 my $sha1_pattern     = qr/[0-9a-fA-F]{40}/; # pattern to validate Git sha1sums
 my $dummy_commit_msg = 'dummy commit';      # message to skip in recent changes
 
 my $sha1_pattern     = qr/[0-9a-fA-F]{40}/; # pattern to validate Git sha1sums
 my $dummy_commit_msg = 'dummy commit';      # message to skip in recent changes
 
-hook(type => "checkconfig", id => "git", call => sub { #{{{
-       if (! defined $config{diffurl}) {
-               $config{diffurl}="";
-       }
+sub import { #{{{
+       hook(type => "checkconfig", id => "git", call => \&checkconfig);
+       hook(type => "getsetup", id => "git", 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 checkconfig () { #{{{
        if (! defined $config{gitorigin_branch}) {
                $config{gitorigin_branch}="origin";
        }
        if (! defined $config{gitmaster_branch}) {
                $config{gitmaster_branch}="master";
        }
        if (! defined $config{gitorigin_branch}) {
                $config{gitorigin_branch}="origin";
        }
        if (! defined $config{gitmaster_branch}) {
                $config{gitmaster_branch}="master";
        }
-       if (length $config{git_wrapper}) {
+       if (defined $config{git_wrapper} && length $config{git_wrapper}) {
                push @{$config{wrappers}}, {
                        wrapper => $config{git_wrapper},
                        wrappermode => (defined $config{git_wrappermode} ? $config{git_wrappermode} : "06755"),
                };
        }
                push @{$config{wrappers}}, {
                        wrapper => $config{git_wrapper},
                        wrappermode => (defined $config{git_wrappermode} ? $config{git_wrappermode} : "06755"),
                };
        }
-}); #}}}
+} #}}}
 
 
-hook(type => "getsetup", id => "git", call => sub { #{{{
+sub getsetup () { #{{{
        return
        return
+               plugin => {
+                       safe => 0, # rcs plugin
+                       rebuild => undef,
+               },
                git_wrapper => {
                        type => "string",
                        example => "/git/wiki.git/hooks/post-update",
                git_wrapper => {
                        type => "string",
                        example => "/git/wiki.git/hooks/post-update",
-                       description => "git post-update executable to generate",
+                       description => "git hook to generate",
                        safe => 0, # file
                        rebuild => 0,
                },
                        safe => 0, # file
                        rebuild => 0,
                },
@@ -73,9 +88,9 @@ hook(type => "getsetup", id => "git", call => sub { #{{{
                        safe => 0, # paranoia
                        rebuild => 0,
                },
                        safe => 0, # paranoia
                        rebuild => 0,
                },
-}); #}}}
+} #}}}
 
 
-sub _safe_git (&@) { #{{{
+sub safe_git (&@) { #{{{
        # Start a child process safely without resorting /bin/sh.
        # Return command output or success state (in scalar context).
 
        # Start a child process safely without resorting /bin/sh.
        # Return command output or success state (in scalar context).
 
@@ -107,12 +122,12 @@ sub _safe_git (&@) { #{{{
        return wantarray ? @lines : ($? == 0);
 }
 # Convenient wrappers.
        return wantarray ? @lines : ($? == 0);
 }
 # Convenient wrappers.
-sub run_or_die ($@) { _safe_git(\&error, @_) }
-sub run_or_cry ($@) { _safe_git(sub { warn @_ },  @_) }
-sub run_or_non ($@) { _safe_git(undef,            @_) }
+sub run_or_die ($@) { safe_git(\&error, @_) }
+sub run_or_cry ($@) { safe_git(sub { warn @_ },  @_) }
+sub run_or_non ($@) { safe_git(undef,            @_) }
 #}}}
 
 #}}}
 
-sub _merge_past ($$$) { #{{{
+sub merge_past ($$$) { #{{{
        # Unlike with Subversion, Git cannot make a 'svn merge -rN:M file'.
        # Git merge commands work with the committed changes, except in the
        # implicit case of '-m' of git checkout(1).  So we should invent a
        # Unlike with Subversion, Git cannot make a 'svn merge -rN:M file'.
        # Git merge commands work with the committed changes, except in the
        # implicit case of '-m' of git checkout(1).  So we should invent a
@@ -206,7 +221,7 @@ sub _merge_past ($$$) { #{{{
        return $conflict;
 } #}}}
 
        return $conflict;
 } #}}}
 
-sub _parse_diff_tree ($@) { #{{{
+sub parse_diff_tree ($@) { #{{{
        # Parse the raw diff tree chunk and return the info hash.
        # See git-diff-tree(1) for the syntax.
 
        # Parse the raw diff tree chunk and return the info hash.
        # See git-diff-tree(1) for the syntax.
 
@@ -293,13 +308,16 @@ sub _parse_diff_tree ($@) { #{{{
                        my $sha1_to = shift(@tmp);
                        my $status = shift(@tmp);
 
                        my $sha1_to = shift(@tmp);
                        my $status = shift(@tmp);
 
+                       # git does not output utf-8 filenames, but instead
+                       # double-quotes them with the utf-8 characters
+                       # escaped as \nnn\nnn.
                        if ($file =~ m/^"(.*)"$/) {
                                ($file=$1) =~ s/\\([0-7]{1,3})/chr(oct($1))/eg;
                        }
                        $file =~ s/^\Q$prefix\E//;
                        if (length $file) {
                                push @{ $ci{'details'} }, {
                        if ($file =~ m/^"(.*)"$/) {
                                ($file=$1) =~ s/\\([0-7]{1,3})/chr(oct($1))/eg;
                        }
                        $file =~ s/^\Q$prefix\E//;
                        if (length $file) {
                                push @{ $ci{'details'} }, {
-                                       'file'      => decode_utf8($file),
+                                       'file'      => decode("utf8", $file),
                                        'sha1_from' => $sha1_from[0],
                                        'sha1_to'   => $sha1_to,
                                };
                                        'sha1_from' => $sha1_from[0],
                                        'sha1_to'   => $sha1_to,
                                };
@@ -326,7 +344,7 @@ sub git_commit_info ($;$) { #{{{
        my ($prefix) = run_or_die('git', 'rev-parse', '--show-prefix');
 
        my @ci;
        my ($prefix) = run_or_die('git', 'rev-parse', '--show-prefix');
 
        my @ci;
-       while (my $parsed = _parse_diff_tree(($prefix or ""), \@raw_lines)) {
+       while (my $parsed = parse_diff_tree(($prefix or ""), \@raw_lines)) {
                push @ci, $parsed;
        }
 
                push @ci, $parsed;
        }
 
@@ -379,7 +397,7 @@ sub rcs_commit ($$$;$$) { #{{{
        my ($prev) = $rcstoken =~ /^($sha1_pattern)$/; # untaint
 
        if (defined $cur && defined $prev && $cur ne $prev) {
        my ($prev) = $rcstoken =~ /^($sha1_pattern)$/; # untaint
 
        if (defined $cur && defined $prev && $cur ne $prev) {
-               my $conflict = _merge_past($prev, $file, $dummy_commit_msg);
+               my $conflict = merge_past($prev, $file, $dummy_commit_msg);
                return $conflict if defined $conflict;
        }
 
                return $conflict if defined $conflict;
        }
 
@@ -400,11 +418,23 @@ sub rcs_commit_staged ($$$) {
                $ENV{GIT_AUTHOR_EMAIL}="$u\@web";
        }
 
                $ENV{GIT_AUTHOR_EMAIL}="$u\@web";
        }
 
+       $message = IkiWiki::possibly_foolish_untaint($message);
+       my @opts;
+       if ($message !~ /\S/) {
+               # Force git to allow empty commit messages.
+               # (If this version of git supports it.)
+               my ($version)=`git --version` =~ /git version (.*)/;
+               if ($version ge "1.5.4") {
+                       push @opts, '--cleanup=verbatim';
+               }
+               else {
+                       $message.=".";
+               }
+       }
+       push @opts, '-q';
        # git commit returns non-zero if file has not been really changed.
        # so we should ignore its exit status (hence run_or_non).
        # git commit returns non-zero if file has not been really changed.
        # so we should ignore its exit status (hence run_or_non).
-       $message = possibly_foolish_untaint($message);
-       if (run_or_non('git', 'commit', '--cleanup=verbatim',
-                      '-q', '-m', $message)) {
+       if (run_or_non('git', 'commit', @opts, '-m', $message)) {
                if (length $config{gitorigin_branch}) {
                        run_or_cry('git', 'push', $config{gitorigin_branch});
                }
                if (length $config{gitorigin_branch}) {
                        run_or_cry('git', 'push', $config{gitorigin_branch});
                }
@@ -458,7 +488,7 @@ sub rcs_recentchanges ($) { #{{{
                foreach my $detail (@{ $ci->{'details'} }) {
                        my $file = $detail->{'file'};
 
                foreach my $detail (@{ $ci->{'details'} }) {
                        my $file = $detail->{'file'};
 
-                       my $diffurl = $config{'diffurl'};
+                       my $diffurl = defined $config{'diffurl'} ? $config{'diffurl'} : "";
                        $diffurl =~ s/\[\[file\]\]/$file/go;
                        $diffurl =~ s/\[\[sha1_parent\]\]/$ci->{'parent'}/go;
                        $diffurl =~ s/\[\[sha1_from\]\]/$detail->{'sha1_from'}/go;
                        $diffurl =~ s/\[\[file\]\]/$file/go;
                        $diffurl =~ s/\[\[sha1_parent\]\]/$ci->{'parent'}/go;
                        $diffurl =~ s/\[\[sha1_from\]\]/$detail->{'sha1_from'}/go;