X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/blobdiff_plain/ad4f7bc075d95f96ceed7d46ef401e93dceddfd7..2bbe1bf9af4ba20f28a17d5e16a467313d2e37bd:/IkiWiki/Rcs/git.pm?ds=inline diff --git a/IkiWiki/Rcs/git.pm b/IkiWiki/Rcs/git.pm index 271104f3e..bcf317002 100644 --- a/IkiWiki/Rcs/git.pm +++ b/IkiWiki/Rcs/git.pm @@ -1,16 +1,59 @@ #!/usr/bin/perl +package IkiWiki; + use warnings; use strict; use IkiWiki; use Encode; use open qw{:utf8 :std}; -package IkiWiki; - 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{gitorigin_branch}) { + $config{gitorigin_branch}="origin"; + } + if (! defined $config{gitmaster_branch}) { + $config{gitmaster_branch}="master"; + } +}); #}}} + +hook(type => "getsetup", id => "git", call => sub { #{{{ + return + historyurl => { + type => "string", + default => "", + example => "http://git.example.com/gitweb.cgi?p=wiki.git;a=history;f=[[file]]", + description => "gitweb url to show file history ([[file]] substituted)", + safe => 1, + rebuild => 1, + }, + diffurl => { + type => "string", + default => "", + example => "http://git.example.com/gitweb.cgi?p=wiki.git;a=blobdiff;h=[[sha1_to]];hp=[[sha1_from]];hb=[[sha1_parent]];f=[[file]]", + description => "gitweb url to show a diff ([[sha1_to]], [[sha1_from]], [[sha1_parent]], and [[file]] substituted)", + safe => 1, + rebuild => 1, + }, + gitorigin_branch => { + type => "string", + default => "origin", + description => "where to pull and push changes (unset to not pull/push)", + safe => 0, # paranoia + rebuild => 0, + }, + gitmaster_branch => { + type => "string", + default => "master", + description => "branch that the wiki is stored in", + safe => 0, # paranoia + rebuild => 0, + }, +}); #}}} + sub _safe_git (&@) { #{{{ # Start a child process safely without resorting /bin/sh. # Return command output or success state (in scalar context). @@ -180,14 +223,14 @@ sub _parse_diff_tree ($@) { #{{{ $ci{ "${who}_epoch" } = $epoch; $ci{ "${who}_tz" } = $tz; - if ($name =~ m/^([^<]+) <([^@>]+)/) { - my ($fullname, $username) = ($1, $2); - $ci{"${who}_fullname"} = $fullname; - $ci{"${who}_username"} = $username; + if ($name =~ m/^[^<]+\s+<([^@>]+)/) { + $ci{"${who}_username"} = $1; + } + elsif ($name =~ m/^([^<]+)\s+<>$/) { + $ci{"${who}_username"} = $1; } else { - $ci{"${who}_fullname"} = - $ci{"${who}_username"} = $name; + $ci{"${who}_username"} = $name; } } elsif ($line =~ m/^$/) { @@ -197,7 +240,7 @@ sub _parse_diff_tree ($@) { #{{{ } debug("No 'tree' seen in diff-tree output") if !defined $ci{'tree'}; - + if (defined $ci{'parents'}) { $ci{'parent'} = @{ $ci{'parents'} }[0]; } @@ -205,15 +248,13 @@ sub _parse_diff_tree ($@) { #{{{ $ci{'parent'} = 0 x 40; } - # Commit message. - while (my $line = shift @{ $dt_ref }) { - if ($line =~ m/^$/) { - # Trailing empty line signals next section. - last; - }; + # Commit message (optional). + while ($dt_ref->[0] =~ /^ /) { + my $line = shift @{ $dt_ref }; $line =~ s/^ //; push @{ $ci{'comment'} }, $line; } + shift @{ $dt_ref } if $dt_ref->[0] =~ /^$/; # Modified files. while (my $line = shift @{ $dt_ref }) { @@ -279,7 +320,8 @@ sub git_sha1 (;$) { #{{{ my $file = shift || q{--}; # Ignore error since a non-existing file might be given. - my ($sha1) = run_or_non('git', 'rev-list', '--max-count=1', 'HEAD', $file); + my ($sha1) = run_or_non('git', 'rev-list', '--max-count=1', 'HEAD', + '--', $file); if ($sha1) { ($sha1) = $sha1 =~ m/($sha1_pattern)/; # sha1 is untainted now } else { debug("Empty sha1sum for '$file'.") } @@ -310,22 +352,6 @@ sub rcs_commit ($$$;$$) { #{{{ my ($file, $message, $rcstoken, $user, $ipaddr) = @_; - if (defined $user) { - $message = "web commit by $user" . - (length $message ? ": $message" : ""); - } - elsif (defined $ipaddr) { - $message = "web commit from $ipaddr" . - (length $message ? ": $message" : ""); - } - - # XXX: Wiki directory is in the unlocked state when starting this - # action. But it takes time for a Git process to finish its job - # (especially if a merge required), so we must re-lock to prevent - # race conditions. Only when the time of the real commit action - # (i.e. git push) comes, we'll unlock the directory. - lockwiki(); - # Check to see if the page has been changed by someone else since # rcs_prepedit was called. my $cur = git_sha1($file); @@ -336,18 +362,36 @@ sub rcs_commit ($$$;$$) { #{{{ return $conflict if defined $conflict; } + rcs_add($file); + return rcs_commit_staged($message, $user, $ipaddr); +} #}}} + +sub rcs_commit_staged ($$$) { + # Commits all staged changes. Changes can be staged using rcs_add, + # rcs_remove, and rcs_rename. + my ($message, $user, $ipaddr)=@_; + + # Set the commit author and email to the web committer. + my %env=%ENV; + if (defined $user || defined $ipaddr) { + my $u=defined $user ? $user : $ipaddr; + $ENV{GIT_AUTHOR_NAME}=$u; + $ENV{GIT_AUTHOR_EMAIL}="$u\@web"; + } + # 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', '-q', '-m', $message, '-i', $file)) { - unlockwiki(); + if (run_or_non('git', 'commit', '--cleanup=verbatim', + '-q', '-m', $message)) { if (length $config{gitorigin_branch}) { run_or_cry('git', 'push', $config{gitorigin_branch}); } } - + + %ENV=%env; return undef; # success -} #}}} +} sub rcs_add ($) { # {{{ # Add file to archive. @@ -357,6 +401,20 @@ sub rcs_add ($) { # {{{ run_or_cry('git', 'add', $file); } #}}} +sub rcs_remove ($) { # {{{ + # Remove file from archive. + + my ($file) = @_; + + run_or_cry('git', 'rm', '-f', $file); +} #}}} + +sub rcs_rename ($$) { # {{{ + my ($src, $dest) = @_; + + run_or_cry('git', 'mv', '-f', $src, $dest); +} #}}} + sub rcs_recentchanges ($) { #{{{ # List of recent changes. @@ -368,14 +426,14 @@ sub rcs_recentchanges ($) { #{{{ my @rets; foreach my $ci (git_commit_info('HEAD', $num)) { # Skip redundant commits. - next if (@{$ci->{'comment'}}[0] eq $dummy_commit_msg); + next if ($ci->{'comment'} && @{$ci->{'comment'}}[0] eq $dummy_commit_msg); my ($sha1, $when) = ( $ci->{'sha1'}, - time - $ci->{'author_epoch'} + $ci->{'author_epoch'} ); - my (@pages, @messages); + my @pages; foreach my $detail (@{ $ci->{'details'} }) { my $file = $detail->{'file'}; @@ -390,24 +448,31 @@ sub rcs_recentchanges ($) { #{{{ diffurl => $diffurl, }; } - push @messages, { line => $_ } foreach @{$ci->{'comment'}}; - my ($user, $type) = (q{}, "web"); + my @messages; + my $pastblank=0; + foreach my $line (@{$ci->{'comment'}}) { + $pastblank=1 if $line eq ''; + next if $pastblank && $line=~m/^ *(signed[ \-]off[ \-]by[ :]|acked[ \-]by[ :]|cc[ :])/i; + push @messages, { line => $line }; + } - if (defined $messages[0] && - $messages[0]->{line} =~ m/$config{web_commit_regexp}/) { + my $user=$ci->{'author_username'}; + my $web_commit = ($ci->{'author'} =~ /\@web>/); + + # compatability code for old web commit messages + if (! $web_commit && + defined $messages[0] && + $messages[0]->{line} =~ m/$config{web_commit_regexp}/) { $user = defined $2 ? "$2" : "$3"; $messages[0]->{line} = $4; - } - else { - $type ="git"; - $user = $ci->{'author_username'}; + $web_commit=1; } push @rets, { rev => $sha1, user => $user, - committype => $type, + committype => $web_commit ? "web" : "git", when => $when, message => [@messages], pages => [@pages], @@ -419,45 +484,21 @@ sub rcs_recentchanges ($) { #{{{ return @rets; } #}}} -sub rcs_notify () { #{{{ - # Send notification mail to subscribed users. - # - # In usual Git usage, hooks/update script is presumed to send - # notification mails (see git-receive-pack(1)). But we prefer - # hooks/post-update to support IkiWiki commits coming from a - # cloned repository (through command line) because post-update - # is called _after_ each ref in repository is updated (update - # hook is called _before_ the repository is updated). - # - # Here, we rely on a simple fact: we can extract all parts of the - # notification content by parsing the "HEAD" commit. - - my $ci = git_commit_info('HEAD'); - return if !defined $ci; - - my @changed_pages = map { $_->{'file'} } @{ $ci->{'details'} }; - - my ($user, $message); - if (@{ $ci->{'comment'} }[0] =~ m/$config{web_commit_regexp}/) { - $user = defined $2 ? $2 : $3; - $message = $4; +sub rcs_diff ($) { #{{{ + my $rev=shift; + my ($sha1) = $rev =~ /^($sha1_pattern)$/; # untaint + my @lines; + foreach my $line (run_or_non("git", "show", $sha1)) { + if (@lines || $line=~/^diff --git/) { + push @lines, $line."\n"; + } + } + if (wantarray) { + return @lines; } else { - $user = $ci->{'author_username'}; - $message = join "\n", @{ $ci->{'comment'} }; + return join("", @lines); } - - my $sha1 = $ci->{'sha1'}; - - require IkiWiki::UserInfo; - send_commit_mails( - sub { - $message; - }, - sub { - join "\n", run_or_die('git', 'diff', "${sha1}^", $sha1); - }, $user, @changed_pages - ); } #}}} sub rcs_getctime ($) { #{{{