X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/blobdiff_plain/5c6f7a8d1b805001a3d1ee912683755cec773682..1572c3c376df36ea09e27a1ea437e3a75cdf0f84:/IkiWiki/Plugin/git.pm diff --git a/IkiWiki/Plugin/git.pm b/IkiWiki/Plugin/git.pm index 48e71aa9a..3117e4291 100644 --- a/IkiWiki/Plugin/git.pm +++ b/IkiWiki/Plugin/git.pm @@ -9,7 +9,7 @@ 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 $no_chdir=0; +my $git_dir=undef; sub import { hook(type => "checkconfig", id => "git", call => \&checkconfig); @@ -152,10 +152,11 @@ sub genwrapper { } 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 to /bin/sh. + # Returns command output (in list content) or success state + # (in scalar context), or runs the specified data handler. - my ($error_handler, @cmdline) = @_; + my ($error_handler, $data_handler, @cmdline) = @_; my $pid = open my $OUT, "-|"; @@ -164,9 +165,13 @@ sub safe_git (&@) { if (!$pid) { # In child. # Git commands want to be in wc. - if (! $no_chdir) { + if (! defined $git_dir) { chdir $config{srcdir} - or error("Cannot chdir to $config{srcdir}: $!"); + or error("cannot chdir to $config{srcdir}: $!"); + } + else { + chdir $git_dir + or error("cannot chdir to $git_dir: $!"); } exec @cmdline or error("Cannot exec '@cmdline': $!"); } @@ -183,7 +188,12 @@ sub safe_git (&@) { chomp; - push @lines, $_; + if (! defined $data_handler) { + push @lines, $_; + } + else { + last unless $data_handler->($_); + } } close $OUT; @@ -193,9 +203,9 @@ sub safe_git (&@) { 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, undef, @_) } +sub run_or_cry ($@) { safe_git(sub { warn @_ }, undef, @_) } +sub run_or_non ($@) { safe_git(undef, undef, @_) } sub merge_past ($$$) { @@ -492,16 +502,16 @@ sub rcs_commit (@) { return $conflict if defined $conflict; } - rcs_add($params{file}); - return rcs_commit_staged( - message => $params{message}, - session => $params{session}, - ); + return rcs_commit_helper(@_); } sub rcs_commit_staged (@) { # Commits all staged changes. Changes can be staged using rcs_add, # rcs_remove, and rcs_rename. + return rcs_commit_helper(@_); +} + +sub rcs_commit_helper (@) { my %params=@_; my %env=%ENV; @@ -542,10 +552,12 @@ sub rcs_commit_staged (@) { $params{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). - if (run_or_non('git', 'commit', @opts, '-m', $params{message})) { + if (exists $params{file}) { + push @opts, '--', $params{file}; + } + # git commit returns non-zero if nothing really changed. + # So we should ignore its exit status (hence run_or_non). + if (run_or_non('git', 'commit', '-m', $params{message}, '-q', @opts)) { if (length $config{gitorigin_branch}) { run_or_cry('git', 'push', $config{gitorigin_branch}); } @@ -657,15 +669,19 @@ sub rcs_recentchanges ($) { return @rets; } -sub rcs_diff ($) { +sub rcs_diff ($;$) { my $rev=shift; + my $maxlines=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"; - } - } + my $addlines=sub { + my $line=shift; + return if defined $maxlines && @lines == $maxlines; + push @lines, $line."\n" + if (@lines || $line=~/^diff --git/); + return 1; + }; + safe_git(undef, $addlines, "git", "show", $sha1); if (wantarray) { return @lines; } @@ -684,7 +700,7 @@ sub findtimes ($$) { if (! keys %time_cache) { my $date; foreach my $line (run_or_die('git', 'log', - '--pretty=format:%ct', + '--pretty=format:%at', '--name-only', '--relative')) { if (! defined $date && $line =~ /^(\d+)$/) { $date=$line; @@ -721,14 +737,13 @@ sub rcs_getmtime ($) { } { -my $git_root; - +my $ret; sub git_find_root { # The wiki may not be the only thing in the git repo. # Determine if it is in a subdirectory by examining the srcdir, # and its parents, looking for the .git directory. - return $git_root if defined $git_root; + return @$ret if defined $ret; my $subdir=""; my $dir=$config{srcdir}; @@ -740,15 +755,17 @@ sub git_find_root { } } - return $git_root=$subdir; + $ret=[$subdir, $dir]; + return @$ret; } } sub git_parse_changes { + my $reverted = shift; my @changes = @_; - my $subdir = git_find_root(); + my ($subdir, $rootdir) = git_find_root(); my @rets; foreach my $ci (@changes) { foreach my $detail (@{ $ci->{'details'} }) { @@ -766,11 +783,11 @@ sub git_parse_changes { $mode=$detail->{'mode_to'}; } elsif ($detail->{'status'} =~ /^[AM]+\d*$/) { - $action="add"; + $action= $reverted ? "remove" : "add"; $mode=$detail->{'mode_to'}; } elsif ($detail->{'status'} =~ /^[DAM]+\d*/) { - $action="remove"; + $action= $reverted ? "add" : "remove"; $mode=$detail->{'mode_from'}; } else { @@ -793,11 +810,9 @@ sub git_parse_changes { eval q{use File::Temp}; die $@ if $@; my $fh; - ($fh, $path)=File::Temp::tempfile("XXXXXXXXXX", UNLINK => 1); - # Ensure we run this in the right place, - # see comments in rcs_receive. - my $cmd = ($no_chdir ? '' : "cd $config{srcdir} && ") - . "git show $detail->{sha1_to} > '$path'"; + ($fh, $path)=File::Temp::tempfile(undef, UNLINK => 1); + my $cmd = "cd $git_dir && ". + "git show $detail->{sha1_to} > '$path'"; if (system($cmd) != 0) { error("failed writing temp file '$path'."); } @@ -827,10 +842,12 @@ sub rcs_receive () { # Avoid chdir when running git here, because the changes # are in the master git repo, not the srcdir repo. + # (Also, if a subdir is involved, we don't want to chdir to + # it and only see changes in it.) # The pre-receive hook already puts us in the right place. - $no_chdir=1; - push @rets, git_parse_changes(git_commit_info($oldrev."..".$newrev)); - $no_chdir=0; + $git_dir="."; + push @rets, git_parse_changes(0, git_commit_info($oldrev."..".$newrev)); + $git_dir=undef; } return reverse @rets; @@ -838,20 +855,41 @@ sub rcs_receive () { sub rcs_preprevert ($) { my $rev=shift; + my ($sha1) = $rev =~ /^($sha1_pattern)$/; # untaint + + # Examine changes from root of git repo, not from any subdir, + # in order to see all changes. + my ($subdir, $rootdir) = git_find_root(); + $git_dir=$rootdir; + + my @commits=git_commit_info($sha1, 1); + if (! @commits) { + error "unknown commit"; # just in case + } + + # git revert will fail on merge commits. Add a nice message. + if (exists $commits[0]->{parents} && + @{$commits[0]->{parents}} > 1) { + error gettext("you are not allowed to revert a merge"); + } - return git_parse_changes(git_commit_info($rev, 1)); + my @ret=git_parse_changes(1, @commits); + + $git_dir=undef; + return @ret; } sub rcs_revert ($) { # Try to revert the given rev; returns undef on _success_. my $rev = shift; + my ($sha1) = $rev =~ /^($sha1_pattern)$/; # untaint - if (run_or_non('git', 'revert', '--no-commit', $rev)) { + if (run_or_non('git', 'revert', '--no-commit', $sha1)) { return undef; } else { run_or_die('git', 'reset', '--hard'); - return sprintf(gettext("Failed to revert commit %s"), $rev); + return sprintf(gettext("Failed to revert commit %s"), $sha1); } }