2006-06-02 07:32:20 +02:00
|
|
|
#!/usr/bin/perl
|
2008-07-27 04:27:58 +02:00
|
|
|
package IkiWiki::Plugin::git;
|
2008-07-11 12:07:48 +02:00
|
|
|
|
2006-06-02 07:32:20 +02:00
|
|
|
use warnings;
|
|
|
|
use strict;
|
|
|
|
use IkiWiki;
|
2006-07-05 22:54:10 +02:00
|
|
|
use Encode;
|
|
|
|
use open qw{:utf8 :std};
|
2006-06-02 07:32:20 +02:00
|
|
|
|
|
|
|
my $sha1_pattern = qr/[0-9a-fA-F]{40}/; # pattern to validate Git sha1sums
|
2006-06-03 20:03:41 +02:00
|
|
|
my $dummy_commit_msg = 'dummy commit'; # message to skip in recent changes
|
2006-06-02 07:32:20 +02:00
|
|
|
|
2008-07-27 04:27:58 +02:00
|
|
|
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);
|
2008-10-23 22:29:50 +02:00
|
|
|
hook(type => "rcs", id => "rcs_receive", call => \&rcs_receive);
|
2008-07-27 04:27:58 +02:00
|
|
|
} #}}}
|
|
|
|
|
|
|
|
sub checkconfig () { #{{{
|
2008-07-27 00:29:33 +02:00
|
|
|
if (! defined $config{gitorigin_branch}) {
|
|
|
|
$config{gitorigin_branch}="origin";
|
|
|
|
}
|
|
|
|
if (! defined $config{gitmaster_branch}) {
|
|
|
|
$config{gitmaster_branch}="master";
|
|
|
|
}
|
2008-10-23 02:52:34 +02:00
|
|
|
if (defined $config{git_wrapper} &&
|
|
|
|
length $config{git_wrapper}) {
|
2008-07-27 03:00:11 +02:00
|
|
|
push @{$config{wrappers}}, {
|
|
|
|
wrapper => $config{git_wrapper},
|
|
|
|
wrappermode => (defined $config{git_wrappermode} ? $config{git_wrappermode} : "06755"),
|
|
|
|
};
|
|
|
|
}
|
2008-10-23 02:52:34 +02:00
|
|
|
if (defined $config{git_test_receive_wrapper} &&
|
|
|
|
length $config{git_test_receive_wrapper}) {
|
|
|
|
push @{$config{wrappers}}, {
|
|
|
|
test_receive => 1,
|
|
|
|
wrapper => $config{git_test_receive_wrapper},
|
|
|
|
wrappermode => "0755",
|
|
|
|
};
|
|
|
|
}
|
2008-07-27 04:27:58 +02:00
|
|
|
} #}}}
|
2008-07-27 00:29:33 +02:00
|
|
|
|
2008-07-27 04:27:58 +02:00
|
|
|
sub getsetup () { #{{{
|
2008-07-27 00:10:01 +02:00
|
|
|
return
|
2008-08-03 22:40:12 +02:00
|
|
|
plugin => {
|
|
|
|
safe => 0, # rcs plugin
|
|
|
|
rebuild => undef,
|
|
|
|
},
|
2008-07-27 03:00:11 +02:00
|
|
|
git_wrapper => {
|
|
|
|
type => "string",
|
|
|
|
example => "/git/wiki.git/hooks/post-update",
|
2008-09-11 21:53:25 +02:00
|
|
|
description => "git hook to generate",
|
2008-07-27 03:00:11 +02:00
|
|
|
safe => 0, # file
|
|
|
|
rebuild => 0,
|
|
|
|
},
|
|
|
|
git_wrappermode => {
|
|
|
|
type => "string",
|
|
|
|
example => '06755',
|
|
|
|
description => "mode for git_wrapper (can safely be made suid)",
|
|
|
|
safe => 0,
|
|
|
|
rebuild => 0,
|
|
|
|
},
|
2008-10-23 02:52:34 +02:00
|
|
|
git_test_receive_wrapper => {
|
|
|
|
type => "string",
|
|
|
|
example => "/git/wiki.git/hooks/pre-receive",
|
|
|
|
description => "git pre-receive hook to generate",
|
|
|
|
safe => 0, # file
|
|
|
|
rebuild => 0,
|
|
|
|
},
|
2008-10-23 22:29:50 +02:00
|
|
|
untrusted_committers => {
|
2008-10-23 02:52:34 +02:00
|
|
|
type => "string",
|
|
|
|
example => [],
|
|
|
|
description => "unix users whose commits should be checked by the pre-receive hook",
|
|
|
|
safe => 0,
|
|
|
|
rebuild => 0,
|
|
|
|
},
|
2008-07-27 00:10:01 +02:00
|
|
|
historyurl => {
|
|
|
|
type => "string",
|
|
|
|
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",
|
|
|
|
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",
|
2008-07-27 03:00:11 +02:00
|
|
|
example => "origin",
|
2008-07-27 00:33:18 +02:00
|
|
|
description => "where to pull and push changes (set to empty string to disable)",
|
2008-07-27 00:10:01 +02:00
|
|
|
safe => 0, # paranoia
|
|
|
|
rebuild => 0,
|
|
|
|
},
|
|
|
|
gitmaster_branch => {
|
|
|
|
type => "string",
|
2008-07-27 03:00:11 +02:00
|
|
|
example => "master",
|
2008-07-27 00:10:01 +02:00
|
|
|
description => "branch that the wiki is stored in",
|
|
|
|
safe => 0, # paranoia
|
|
|
|
rebuild => 0,
|
|
|
|
},
|
2008-07-27 04:27:58 +02:00
|
|
|
} #}}}
|
2008-07-27 00:10:01 +02:00
|
|
|
|
2008-07-27 04:27:58 +02:00
|
|
|
sub safe_git (&@) { #{{{
|
2006-06-02 07:32:20 +02:00
|
|
|
# Start a child process safely without resorting /bin/sh.
|
|
|
|
# Return command output or success state (in scalar context).
|
|
|
|
|
|
|
|
my ($error_handler, @cmdline) = @_;
|
|
|
|
|
|
|
|
my $pid = open my $OUT, "-|";
|
|
|
|
|
|
|
|
error("Cannot fork: $!") if !defined $pid;
|
|
|
|
|
|
|
|
if (!$pid) {
|
|
|
|
# In child.
|
|
|
|
# Git commands want to be in wc.
|
|
|
|
chdir $config{srcdir}
|
|
|
|
or error("Cannot chdir to $config{srcdir}: $!");
|
|
|
|
exec @cmdline or error("Cannot exec '@cmdline': $!");
|
|
|
|
}
|
|
|
|
# In parent.
|
|
|
|
|
|
|
|
my @lines;
|
|
|
|
while (<$OUT>) {
|
|
|
|
chomp;
|
|
|
|
push @lines, $_;
|
|
|
|
}
|
|
|
|
|
|
|
|
close $OUT;
|
|
|
|
|
2007-10-11 01:27:11 +02:00
|
|
|
$error_handler->("'@cmdline' failed: $!") if $? && $error_handler;
|
2006-06-02 07:32:20 +02:00
|
|
|
|
|
|
|
return wantarray ? @lines : ($? == 0);
|
|
|
|
}
|
|
|
|
# Convenient wrappers.
|
2008-07-27 04:27:58 +02:00
|
|
|
sub run_or_die ($@) { safe_git(\&error, @_) }
|
|
|
|
sub run_or_cry ($@) { safe_git(sub { warn @_ }, @_) }
|
|
|
|
sub run_or_non ($@) { safe_git(undef, @_) }
|
2006-06-02 07:32:20 +02:00
|
|
|
#}}}
|
|
|
|
|
2008-07-27 04:27:58 +02:00
|
|
|
sub merge_past ($$$) { #{{{
|
2006-06-02 07:32:20 +02:00
|
|
|
# Unlike with Subversion, Git cannot make a 'svn merge -rN:M file'.
|
|
|
|
# Git merge commands work with the committed changes, except in the
|
2007-10-31 22:37:33 +01:00
|
|
|
# implicit case of '-m' of git checkout(1). So we should invent a
|
2006-06-02 07:32:20 +02:00
|
|
|
# kludge here. In principle, we need to create a throw-away branch
|
|
|
|
# in preparing for the merge itself. Since branches are cheap (and
|
|
|
|
# branching is fast), this shouldn't cost high.
|
|
|
|
#
|
|
|
|
# The main problem is the presence of _uncommitted_ local changes. One
|
|
|
|
# possible approach to get rid of this situation could be that we first
|
|
|
|
# make a temporary commit in the master branch and later restore the
|
|
|
|
# initial state (this is possible since Git has the ability to undo a
|
2007-10-31 22:37:33 +01:00
|
|
|
# commit, i.e. 'git reset --soft HEAD^'). The method can be summarized
|
2006-06-02 07:32:20 +02:00
|
|
|
# as follows:
|
|
|
|
#
|
|
|
|
# - create a diff of HEAD:current-sha1
|
|
|
|
# - dummy commit
|
|
|
|
# - create a dummy branch and switch to it
|
|
|
|
# - rewind to past (reset --hard to the current-sha1)
|
|
|
|
# - apply the diff and commit
|
|
|
|
# - switch to master and do the merge with the dummy branch
|
|
|
|
# - make a soft reset (undo the last commit of master)
|
|
|
|
#
|
|
|
|
# The above method has some drawbacks: (1) it needs a redundant commit
|
|
|
|
# just to get rid of local changes, (2) somewhat slow because of the
|
|
|
|
# required system forks. Until someone points a more straight method
|
|
|
|
# (which I would be grateful) I have implemented an alternative method.
|
|
|
|
# In this approach, we hide all the modified files from Git by renaming
|
|
|
|
# them (using the 'rename' builtin) and later restore those files in
|
|
|
|
# the throw-away branch (that is, we put the files themselves instead
|
|
|
|
# of applying a patch).
|
|
|
|
|
|
|
|
my ($sha1, $file, $message) = @_;
|
|
|
|
|
|
|
|
my @undo; # undo stack for cleanup in case of an error
|
|
|
|
my $conflict; # file content with conflict markers
|
|
|
|
|
|
|
|
eval {
|
|
|
|
# Hide local changes from Git by renaming the modified file.
|
|
|
|
# Relative paths must be converted to absolute for renaming.
|
|
|
|
my ($target, $hidden) = (
|
|
|
|
"$config{srcdir}/${file}", "$config{srcdir}/${file}.${sha1}"
|
|
|
|
);
|
|
|
|
rename($target, $hidden)
|
|
|
|
or error("rename '$target' to '$hidden' failed: $!");
|
|
|
|
# Ensure to restore the renamed file on error.
|
|
|
|
push @undo, sub {
|
|
|
|
return if ! -e "$hidden"; # already renamed
|
|
|
|
rename($hidden, $target)
|
2006-06-03 20:03:41 +02:00
|
|
|
or warn "rename '$hidden' to '$target' failed: $!";
|
2006-06-02 07:32:20 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
my $branch = "throw_away_${sha1}"; # supposed to be unique
|
|
|
|
|
|
|
|
# Create a throw-away branch and rewind backward.
|
2007-10-31 22:37:33 +01:00
|
|
|
push @undo, sub { run_or_cry('git', 'branch', '-D', $branch) };
|
|
|
|
run_or_die('git', 'branch', $branch, $sha1);
|
2006-06-02 07:32:20 +02:00
|
|
|
|
|
|
|
# Switch to throw-away branch for the merge operation.
|
|
|
|
push @undo, sub {
|
2007-10-31 22:37:33 +01:00
|
|
|
if (!run_or_cry('git', 'checkout', $config{gitmaster_branch})) {
|
|
|
|
run_or_cry('git', 'checkout','-f',$config{gitmaster_branch});
|
2006-06-02 07:32:20 +02:00
|
|
|
}
|
|
|
|
};
|
2007-10-31 22:37:33 +01:00
|
|
|
run_or_die('git', 'checkout', $branch);
|
2006-06-02 07:32:20 +02:00
|
|
|
|
|
|
|
# Put the modified file in _this_ branch.
|
|
|
|
rename($hidden, $target)
|
|
|
|
or error("rename '$hidden' to '$target' failed: $!");
|
|
|
|
|
|
|
|
# _Silently_ commit all modifications in the current branch.
|
2007-10-31 22:37:33 +01:00
|
|
|
run_or_non('git', 'commit', '-m', $message, '-a');
|
2006-06-02 07:32:20 +02:00
|
|
|
# ... and re-switch to master.
|
2007-10-31 22:37:33 +01:00
|
|
|
run_or_die('git', 'checkout', $config{gitmaster_branch});
|
2006-06-02 07:32:20 +02:00
|
|
|
|
|
|
|
# Attempt to merge without complaining.
|
2007-10-31 22:37:33 +01:00
|
|
|
if (!run_or_non('git', 'pull', '--no-commit', '.', $branch)) {
|
2006-06-02 07:32:20 +02:00
|
|
|
$conflict = readfile($target);
|
2007-10-31 22:37:33 +01:00
|
|
|
run_or_die('git', 'reset', '--hard');
|
2006-06-02 07:32:20 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
my $failure = $@;
|
|
|
|
|
|
|
|
# Process undo stack (in reverse order). By policy cleanup
|
|
|
|
# actions should normally print a warning on failure.
|
|
|
|
while (my $handle = pop @undo) {
|
|
|
|
$handle->();
|
|
|
|
}
|
|
|
|
|
|
|
|
error("Git merge failed!\n$failure\n") if $failure;
|
|
|
|
|
|
|
|
return $conflict;
|
|
|
|
} #}}}
|
|
|
|
|
2008-07-27 04:27:58 +02:00
|
|
|
sub parse_diff_tree ($@) { #{{{
|
2006-06-02 07:32:20 +02:00
|
|
|
# Parse the raw diff tree chunk and return the info hash.
|
|
|
|
# See git-diff-tree(1) for the syntax.
|
|
|
|
|
2007-08-30 04:32:35 +02:00
|
|
|
my ($prefix, $dt_ref) = @_;
|
2006-06-02 07:32:20 +02:00
|
|
|
|
|
|
|
# End of stream?
|
2006-11-26 21:05:57 +01:00
|
|
|
return if !defined @{ $dt_ref } ||
|
|
|
|
!defined @{ $dt_ref }[0] || !length @{ $dt_ref }[0];
|
2006-06-02 07:32:20 +02:00
|
|
|
|
|
|
|
my %ci;
|
|
|
|
# Header line.
|
2007-08-24 20:07:15 +02:00
|
|
|
while (my $line = shift @{ $dt_ref }) {
|
2006-06-19 05:33:23 +02:00
|
|
|
return if $line !~ m/^(.+) ($sha1_pattern)/;
|
2006-06-02 07:32:20 +02:00
|
|
|
|
2006-11-26 21:05:57 +01:00
|
|
|
my $sha1 = $2;
|
2006-06-02 07:32:20 +02:00
|
|
|
$ci{'sha1'} = $sha1;
|
2007-08-24 20:07:15 +02:00
|
|
|
last;
|
2006-06-02 07:32:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Identification lines for the commit.
|
2007-08-24 20:07:15 +02:00
|
|
|
while (my $line = shift @{ $dt_ref }) {
|
2006-06-02 07:32:20 +02:00
|
|
|
# Regexps are semi-stolen from gitweb.cgi.
|
2007-10-31 23:42:14 +01:00
|
|
|
if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) {
|
2006-06-02 07:32:20 +02:00
|
|
|
$ci{'tree'} = $1;
|
2006-11-26 21:07:00 +01:00
|
|
|
}
|
|
|
|
elsif ($line =~ m/^parent ([0-9a-fA-F]{40})$/) {
|
2006-06-02 07:32:20 +02:00
|
|
|
# XXX: collecting in reverse order
|
|
|
|
push @{ $ci{'parents'} }, $1;
|
2006-11-26 21:07:00 +01:00
|
|
|
}
|
|
|
|
elsif ($line =~ m/^(author|committer) (.*) ([0-9]+) (.*)$/) {
|
2006-06-02 07:32:20 +02:00
|
|
|
my ($who, $name, $epoch, $tz) =
|
|
|
|
($1, $2, $3, $4 );
|
|
|
|
|
|
|
|
$ci{ $who } = $name;
|
|
|
|
$ci{ "${who}_epoch" } = $epoch;
|
|
|
|
$ci{ "${who}_tz" } = $tz;
|
|
|
|
|
2008-07-17 22:03:16 +02:00
|
|
|
if ($name =~ m/^[^<]+\s+<([^@>]+)/) {
|
|
|
|
$ci{"${who}_username"} = $1;
|
|
|
|
}
|
|
|
|
elsif ($name =~ m/^([^<]+)\s+<>$/) {
|
|
|
|
$ci{"${who}_username"} = $1;
|
2006-11-26 21:07:00 +01:00
|
|
|
}
|
|
|
|
else {
|
2008-07-17 22:03:16 +02:00
|
|
|
$ci{"${who}_username"} = $name;
|
2006-06-02 07:32:20 +02:00
|
|
|
}
|
2006-11-26 21:07:00 +01:00
|
|
|
}
|
|
|
|
elsif ($line =~ m/^$/) {
|
2006-06-02 07:32:20 +02:00
|
|
|
# Trailing empty line signals next section.
|
2007-08-24 20:07:15 +02:00
|
|
|
last;
|
2006-06-02 07:32:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-11-12 18:53:46 +01:00
|
|
|
debug("No 'tree' seen in diff-tree output") if !defined $ci{'tree'};
|
2008-07-17 22:53:54 +02:00
|
|
|
|
2007-11-12 18:53:46 +01:00
|
|
|
if (defined $ci{'parents'}) {
|
|
|
|
$ci{'parent'} = @{ $ci{'parents'} }[0];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$ci{'parent'} = 0 x 40;
|
|
|
|
}
|
2006-06-02 07:32:20 +02:00
|
|
|
|
2008-07-17 22:53:54 +02:00
|
|
|
# Commit message (optional).
|
|
|
|
while ($dt_ref->[0] =~ /^ /) {
|
|
|
|
my $line = shift @{ $dt_ref };
|
2006-06-02 07:32:20 +02:00
|
|
|
$line =~ s/^ //;
|
|
|
|
push @{ $ci{'comment'} }, $line;
|
|
|
|
}
|
2008-07-17 22:53:54 +02:00
|
|
|
shift @{ $dt_ref } if $dt_ref->[0] =~ /^$/;
|
2006-06-02 07:32:20 +02:00
|
|
|
|
|
|
|
# Modified files.
|
2007-08-24 20:07:15 +02:00
|
|
|
while (my $line = shift @{ $dt_ref }) {
|
2008-01-17 13:42:34 +01:00
|
|
|
if ($line =~ m{^
|
|
|
|
(:+) # number of parents
|
|
|
|
([^\t]+)\t # modes, sha1, status
|
|
|
|
(.*) # file names
|
2006-06-02 07:32:20 +02:00
|
|
|
$}xo) {
|
2008-01-17 13:42:34 +01:00
|
|
|
my $num_parents = length $1;
|
|
|
|
my @tmp = split(" ", $2);
|
|
|
|
my ($file, $file_to) = split("\t", $3);
|
|
|
|
my @mode_from = splice(@tmp, 0, $num_parents);
|
|
|
|
my $mode_to = shift(@tmp);
|
|
|
|
my @sha1_from = splice(@tmp, 0, $num_parents);
|
|
|
|
my $sha1_to = shift(@tmp);
|
|
|
|
my $status = shift(@tmp);
|
2006-06-02 07:32:20 +02:00
|
|
|
|
git: Fix handling of utf-8 filenames in recentchanges.
Seems that the problem is that once the \nnn coming from git is converted
to a single character, decode_utf8 decides that this is a standalone
character, and not part of a multibyte utf-8 sequence, and so does nothing.
I tried playing with the utf-8 flag, but that didn't work. Instead, use
decode("utf8"), which doesn't have the same qualms, and successfully
decodes the octets into a utf-8 character.
Rant:
Think for a minute about fact that any and every program that parses git-log,
or git-show, etc output to figure out what files were in a commit needs to
contain this snippet of code, to convert from git-log's wacky output to a
regular character set:
if ($file =~ m/^"(.*)"$/) {
($file=$1) =~ s/\\([0-7]{1,3})/chr(oct($1))/eg;
}
(And it's only that "simple" if you don't care about filenames with
embedded \n or \t or other control characters.)
Does that strike anyone else as putting the parsing and conversion in the
wrong place (ie, in gitweb, ikiwiki, etc, etc)? Doesn't anyone who actually
uses git with utf-8 filenames get a bit pissed off at seeing \xxx\xxx
instead of the utf-8 in git-commit and other output?
2008-09-26 00:26:42 +02:00
|
|
|
# git does not output utf-8 filenames, but instead
|
|
|
|
# double-quotes them with the utf-8 characters
|
|
|
|
# escaped as \nnn\nnn.
|
2006-06-02 07:32:20 +02:00
|
|
|
if ($file =~ m/^"(.*)"$/) {
|
|
|
|
($file=$1) =~ s/\\([0-7]{1,3})/chr(oct($1))/eg;
|
|
|
|
}
|
2007-08-30 04:32:35 +02:00
|
|
|
$file =~ s/^\Q$prefix\E//;
|
2006-06-02 07:32:20 +02:00
|
|
|
if (length $file) {
|
|
|
|
push @{ $ci{'details'} }, {
|
git: Fix handling of utf-8 filenames in recentchanges.
Seems that the problem is that once the \nnn coming from git is converted
to a single character, decode_utf8 decides that this is a standalone
character, and not part of a multibyte utf-8 sequence, and so does nothing.
I tried playing with the utf-8 flag, but that didn't work. Instead, use
decode("utf8"), which doesn't have the same qualms, and successfully
decodes the octets into a utf-8 character.
Rant:
Think for a minute about fact that any and every program that parses git-log,
or git-show, etc output to figure out what files were in a commit needs to
contain this snippet of code, to convert from git-log's wacky output to a
regular character set:
if ($file =~ m/^"(.*)"$/) {
($file=$1) =~ s/\\([0-7]{1,3})/chr(oct($1))/eg;
}
(And it's only that "simple" if you don't care about filenames with
embedded \n or \t or other control characters.)
Does that strike anyone else as putting the parsing and conversion in the
wrong place (ie, in gitweb, ikiwiki, etc, etc)? Doesn't anyone who actually
uses git with utf-8 filenames get a bit pissed off at seeing \xxx\xxx
instead of the utf-8 in git-commit and other output?
2008-09-26 00:26:42 +02:00
|
|
|
'file' => decode("utf8", $file),
|
2008-01-17 13:42:34 +01:00
|
|
|
'sha1_from' => $sha1_from[0],
|
2006-06-02 07:32:20 +02:00
|
|
|
'sha1_to' => $sha1_to,
|
2008-10-23 02:52:34 +02:00
|
|
|
'mode_from' => $mode_from[0],
|
|
|
|
'mode_to' => $mode_to,
|
|
|
|
'status' => $status,
|
2006-06-02 07:32:20 +02:00
|
|
|
};
|
|
|
|
}
|
2007-08-24 20:07:15 +02:00
|
|
|
next;
|
2006-06-02 07:32:20 +02:00
|
|
|
};
|
2007-08-24 20:07:15 +02:00
|
|
|
last;
|
2006-06-02 07:32:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return \%ci;
|
|
|
|
} #}}}
|
|
|
|
|
2006-11-26 21:05:57 +01:00
|
|
|
sub git_commit_info ($;$) { #{{{
|
2008-10-23 02:52:34 +02:00
|
|
|
# Return an array of commit info hashes of num commits
|
2006-11-26 21:05:57 +01:00
|
|
|
# starting from the given sha1sum.
|
2006-06-02 07:32:20 +02:00
|
|
|
my ($sha1, $num) = @_;
|
|
|
|
|
2008-10-23 02:52:34 +02:00
|
|
|
my @raw_lines = run_or_die('git', 'log',
|
|
|
|
(defined $num ? "--max-count=$num" : ""),
|
2007-10-31 23:32:22 +01:00
|
|
|
'--pretty=raw', '--raw', '--abbrev=40', '--always', '-c',
|
2007-10-22 00:59:18 +02:00
|
|
|
'-r', $sha1, '--', '.');
|
2007-10-31 22:37:33 +01:00
|
|
|
my ($prefix) = run_or_die('git', 'rev-parse', '--show-prefix');
|
2006-06-02 07:32:20 +02:00
|
|
|
|
|
|
|
my @ci;
|
2008-07-27 04:27:58 +02:00
|
|
|
while (my $parsed = parse_diff_tree(($prefix or ""), \@raw_lines)) {
|
2006-06-02 07:32:20 +02:00
|
|
|
push @ci, $parsed;
|
|
|
|
}
|
|
|
|
|
2006-06-19 05:33:23 +02:00
|
|
|
warn "Cannot parse commit info for '$sha1' commit" if !@ci;
|
|
|
|
|
2006-06-02 07:32:20 +02:00
|
|
|
return wantarray ? @ci : $ci[0];
|
|
|
|
} #}}}
|
|
|
|
|
|
|
|
sub git_sha1 (;$) { #{{{
|
|
|
|
# Return head sha1sum (of given file).
|
|
|
|
my $file = shift || q{--};
|
|
|
|
|
2006-06-03 20:03:41 +02:00
|
|
|
# Ignore error since a non-existing file might be given.
|
2008-05-02 19:03:42 +02:00
|
|
|
my ($sha1) = run_or_non('git', 'rev-list', '--max-count=1', 'HEAD',
|
|
|
|
'--', $file);
|
2006-06-03 20:03:41 +02:00
|
|
|
if ($sha1) {
|
|
|
|
($sha1) = $sha1 =~ m/($sha1_pattern)/; # sha1 is untainted now
|
|
|
|
} else { debug("Empty sha1sum for '$file'.") }
|
|
|
|
return defined $sha1 ? $sha1 : q{};
|
2006-06-02 07:32:20 +02:00
|
|
|
} #}}}
|
|
|
|
|
|
|
|
sub rcs_update () { #{{{
|
|
|
|
# Update working directory.
|
|
|
|
|
2007-10-22 03:47:30 +02:00
|
|
|
if (length $config{gitorigin_branch}) {
|
2007-10-31 22:37:33 +01:00
|
|
|
run_or_cry('git', 'pull', $config{gitorigin_branch});
|
2007-10-22 03:47:30 +02:00
|
|
|
}
|
2006-06-02 07:32:20 +02:00
|
|
|
} #}}}
|
|
|
|
|
|
|
|
sub rcs_prepedit ($) { #{{{
|
|
|
|
# Return the commit sha1sum of the file when editing begins.
|
|
|
|
# This will be later used in rcs_commit if a merge is required.
|
|
|
|
my ($file) = @_;
|
|
|
|
|
2006-06-03 20:03:41 +02:00
|
|
|
return git_sha1($file);
|
2006-06-02 07:32:20 +02:00
|
|
|
} #}}}
|
|
|
|
|
2006-11-22 15:28:38 +01:00
|
|
|
sub rcs_commit ($$$;$$) { #{{{
|
2006-06-02 07:32:20 +02:00
|
|
|
# Try to commit the page; returns undef on _success_ and
|
|
|
|
# a version of the page with the rcs's conflict markers on
|
|
|
|
# failure.
|
|
|
|
|
2006-11-22 15:28:38 +01:00
|
|
|
my ($file, $message, $rcstoken, $user, $ipaddr) = @_;
|
|
|
|
|
2006-06-02 07:32:20 +02:00
|
|
|
# Check to see if the page has been changed by someone else since
|
|
|
|
# rcs_prepedit was called.
|
|
|
|
my $cur = git_sha1($file);
|
2006-06-03 20:03:41 +02:00
|
|
|
my ($prev) = $rcstoken =~ /^($sha1_pattern)$/; # untaint
|
2006-06-02 07:32:20 +02:00
|
|
|
|
|
|
|
if (defined $cur && defined $prev && $cur ne $prev) {
|
2008-07-27 04:27:58 +02:00
|
|
|
my $conflict = merge_past($prev, $file, $dummy_commit_msg);
|
2006-06-02 07:32:20 +02:00
|
|
|
return $conflict if defined $conflict;
|
|
|
|
}
|
2008-07-22 22:14:33 +02:00
|
|
|
|
|
|
|
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)=@_;
|
|
|
|
|
2008-07-18 01:12:34 +02:00
|
|
|
# Set the commit author and email to the web committer.
|
2008-07-17 22:03:16 +02:00
|
|
|
my %env=%ENV;
|
|
|
|
if (defined $user || defined $ipaddr) {
|
2008-07-18 01:12:34 +02:00
|
|
|
my $u=defined $user ? $user : $ipaddr;
|
|
|
|
$ENV{GIT_AUTHOR_NAME}=$u;
|
|
|
|
$ENV{GIT_AUTHOR_EMAIL}="$u\@web";
|
2008-07-17 22:03:16 +02:00
|
|
|
}
|
2006-06-02 07:32:20 +02:00
|
|
|
|
2008-08-01 01:35:37 +02:00
|
|
|
$message = IkiWiki::possibly_foolish_untaint($message);
|
2008-08-01 01:23:54 +02:00
|
|
|
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';
|
2007-10-31 22:37:33 +01:00
|
|
|
# git commit returns non-zero if file has not been really changed.
|
2006-06-02 07:32:20 +02:00
|
|
|
# so we should ignore its exit status (hence run_or_non).
|
2008-08-01 01:23:54 +02:00
|
|
|
if (run_or_non('git', 'commit', @opts, '-m', $message)) {
|
2007-10-22 03:47:30 +02:00
|
|
|
if (length $config{gitorigin_branch}) {
|
2007-10-31 22:37:33 +01:00
|
|
|
run_or_cry('git', 'push', $config{gitorigin_branch});
|
2007-10-22 03:47:30 +02:00
|
|
|
}
|
2006-06-02 07:32:20 +02:00
|
|
|
}
|
2008-07-17 22:03:16 +02:00
|
|
|
|
|
|
|
%ENV=%env;
|
2006-06-02 07:32:20 +02:00
|
|
|
return undef; # success
|
2008-07-22 22:14:33 +02:00
|
|
|
}
|
2006-06-02 07:32:20 +02:00
|
|
|
|
|
|
|
sub rcs_add ($) { # {{{
|
|
|
|
# Add file to archive.
|
|
|
|
|
|
|
|
my ($file) = @_;
|
|
|
|
|
2007-10-31 22:37:33 +01:00
|
|
|
run_or_cry('git', 'add', $file);
|
2006-06-02 07:32:20 +02:00
|
|
|
} #}}}
|
|
|
|
|
2008-07-21 19:40:06 +02:00
|
|
|
sub rcs_remove ($) { # {{{
|
|
|
|
# Remove file from archive.
|
|
|
|
|
|
|
|
my ($file) = @_;
|
|
|
|
|
|
|
|
run_or_cry('git', 'rm', '-f', $file);
|
|
|
|
} #}}}
|
|
|
|
|
2008-07-22 22:14:33 +02:00
|
|
|
sub rcs_rename ($$) { # {{{
|
|
|
|
my ($src, $dest) = @_;
|
|
|
|
|
|
|
|
run_or_cry('git', 'mv', '-f', $src, $dest);
|
|
|
|
} #}}}
|
|
|
|
|
2006-06-02 07:32:20 +02:00
|
|
|
sub rcs_recentchanges ($) { #{{{
|
|
|
|
# List of recent changes.
|
|
|
|
|
|
|
|
my ($num) = @_;
|
|
|
|
|
|
|
|
eval q{use Date::Parse};
|
2006-11-08 22:03:33 +01:00
|
|
|
error($@) if $@;
|
2006-06-02 07:32:20 +02:00
|
|
|
|
2006-11-26 21:05:57 +01:00
|
|
|
my @rets;
|
2008-10-23 02:52:34 +02:00
|
|
|
foreach my $ci (git_commit_info('HEAD', $num || 1)) {
|
2006-06-02 07:32:20 +02:00
|
|
|
# Skip redundant commits.
|
2008-07-17 22:53:54 +02:00
|
|
|
next if ($ci->{'comment'} && @{$ci->{'comment'}}[0] eq $dummy_commit_msg);
|
2006-06-02 07:32:20 +02:00
|
|
|
|
2006-11-26 21:05:57 +01:00
|
|
|
my ($sha1, $when) = (
|
|
|
|
$ci->{'sha1'},
|
2008-01-29 04:57:22 +01:00
|
|
|
$ci->{'author_epoch'}
|
2006-11-26 21:05:57 +01:00
|
|
|
);
|
2006-06-02 07:32:20 +02:00
|
|
|
|
2008-07-17 22:03:16 +02:00
|
|
|
my @pages;
|
2007-08-24 20:07:15 +02:00
|
|
|
foreach my $detail (@{ $ci->{'details'} }) {
|
2007-10-22 00:59:18 +02:00
|
|
|
my $file = $detail->{'file'};
|
2006-06-02 07:32:20 +02:00
|
|
|
|
2008-07-27 06:54:15 +02:00
|
|
|
my $diffurl = defined $config{'diffurl'} ? $config{'diffurl'} : "";
|
2006-06-02 07:32:20 +02:00
|
|
|
$diffurl =~ s/\[\[file\]\]/$file/go;
|
|
|
|
$diffurl =~ s/\[\[sha1_parent\]\]/$ci->{'parent'}/go;
|
2006-10-14 05:10:33 +02:00
|
|
|
$diffurl =~ s/\[\[sha1_from\]\]/$detail->{'sha1_from'}/go;
|
|
|
|
$diffurl =~ s/\[\[sha1_to\]\]/$detail->{'sha1_to'}/go;
|
2006-06-02 07:32:20 +02:00
|
|
|
|
|
|
|
push @pages, {
|
2006-09-03 21:53:23 +02:00
|
|
|
page => pagename($file),
|
2006-06-02 07:32:20 +02:00
|
|
|
diffurl => $diffurl,
|
2006-10-14 05:10:33 +02:00
|
|
|
};
|
2006-06-02 07:32:20 +02:00
|
|
|
}
|
2008-05-16 00:03:44 +02:00
|
|
|
|
2008-07-17 22:03:16 +02:00
|
|
|
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 };
|
|
|
|
}
|
2006-06-02 07:32:20 +02:00
|
|
|
|
2008-07-17 22:35:18 +02:00
|
|
|
my $user=$ci->{'author_username'};
|
2008-07-18 01:23:00 +02:00
|
|
|
my $web_commit = ($ci->{'author'} =~ /\@web>/);
|
2008-07-17 22:35:18 +02:00
|
|
|
|
2008-07-17 22:03:16 +02:00
|
|
|
# compatability code for old web commit messages
|
2008-07-17 22:35:18 +02:00
|
|
|
if (! $web_commit &&
|
|
|
|
defined $messages[0] &&
|
|
|
|
$messages[0]->{line} =~ m/$config{web_commit_regexp}/) {
|
2006-11-26 21:07:00 +01:00
|
|
|
$user = defined $2 ? "$2" : "$3";
|
|
|
|
$messages[0]->{line} = $4;
|
2008-07-18 01:25:10 +02:00
|
|
|
$web_commit=1;
|
2006-11-26 21:07:00 +01:00
|
|
|
}
|
2006-06-02 07:32:20 +02:00
|
|
|
|
|
|
|
push @rets, {
|
2008-01-29 03:23:56 +01:00
|
|
|
rev => $sha1,
|
2006-09-03 21:53:23 +02:00
|
|
|
user => $user,
|
2008-07-17 22:03:16 +02:00
|
|
|
committype => $web_commit ? "web" : "git",
|
2006-06-02 07:32:20 +02:00
|
|
|
when => $when,
|
2006-11-26 21:05:57 +01:00
|
|
|
message => [@messages],
|
2006-06-02 07:32:20 +02:00
|
|
|
pages => [@pages],
|
2007-10-31 23:32:22 +01:00
|
|
|
} if @pages;
|
2006-06-02 07:32:20 +02:00
|
|
|
|
2007-08-24 20:07:15 +02:00
|
|
|
last if @rets >= $num;
|
2006-06-02 07:32:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return @rets;
|
|
|
|
} #}}}
|
|
|
|
|
2008-03-03 21:53:34 +01:00
|
|
|
sub rcs_diff ($) { #{{{
|
|
|
|
my $rev=shift;
|
|
|
|
my ($sha1) = $rev =~ /^($sha1_pattern)$/; # untaint
|
2008-03-12 20:45:10 +01:00
|
|
|
my @lines;
|
2008-03-12 19:44:20 +01:00
|
|
|
foreach my $line (run_or_non("git", "show", $sha1)) {
|
2008-03-12 20:45:10 +01:00
|
|
|
if (@lines || $line=~/^diff --git/) {
|
|
|
|
push @lines, $line."\n";
|
2008-03-12 19:44:20 +01:00
|
|
|
}
|
|
|
|
}
|
2008-03-12 20:45:10 +01:00
|
|
|
if (wantarray) {
|
|
|
|
return @lines;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return join("", @lines);
|
|
|
|
}
|
2008-03-03 21:53:34 +01:00
|
|
|
} #}}}
|
|
|
|
|
2006-06-02 07:32:20 +02:00
|
|
|
sub rcs_getctime ($) { #{{{
|
2007-10-10 20:15:15 +02:00
|
|
|
my $file=shift;
|
|
|
|
# Remove srcdir prefix
|
|
|
|
$file =~ s/^\Q$config{srcdir}\E\/?//;
|
2006-06-02 07:32:20 +02:00
|
|
|
|
|
|
|
my $sha1 = git_sha1($file);
|
2008-10-23 02:52:34 +02:00
|
|
|
my $ci = git_commit_info($sha1, 1);
|
2006-06-02 07:32:20 +02:00
|
|
|
my $ctime = $ci->{'author_epoch'};
|
2007-10-04 11:00:08 +02:00
|
|
|
debug("ctime for '$file': ". localtime($ctime));
|
2006-06-02 07:32:20 +02:00
|
|
|
|
|
|
|
return $ctime;
|
|
|
|
} #}}}
|
|
|
|
|
2008-10-23 22:29:50 +02:00
|
|
|
sub rcs_receive () { #{{{
|
2008-10-23 02:52:34 +02:00
|
|
|
# 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.
|
|
|
|
my $subdir="";
|
|
|
|
my $dir=$config{srcdir};
|
|
|
|
while (! -d "$dir/.git") {
|
|
|
|
$subdir=IkiWiki::basename($dir)."/".$subdir;
|
|
|
|
$dir=IkiWiki::dirname($dir);
|
|
|
|
if (! length $dir) {
|
|
|
|
error("cannot determine root of git repo");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-10-23 22:29:50 +02:00
|
|
|
my @rets;
|
2008-10-23 02:52:34 +02:00
|
|
|
while (<>) {
|
|
|
|
chomp;
|
|
|
|
my ($oldrev, $newrev, $refname) = split(' ', $_, 3);
|
|
|
|
|
|
|
|
# only allow changes to gitmaster_branch
|
|
|
|
if ($refname !~ /^refs\/heads\/\Q$config{gitmaster_branch}\E$/) {
|
2008-10-23 22:29:50 +02:00
|
|
|
error sprintf(gettext("you are not allowed to change %s"), $refname);
|
2008-10-23 02:52:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
foreach my $ci (git_commit_info($oldrev."..".$newrev)) {
|
|
|
|
foreach my $detail (@{ $ci->{'details'} }) {
|
|
|
|
my $file = $detail->{'file'};
|
|
|
|
|
2008-10-23 22:29:50 +02:00
|
|
|
# check that all changed files are in the
|
|
|
|
# subdir
|
2008-10-23 02:52:34 +02:00
|
|
|
if (length $subdir &&
|
|
|
|
! ($file =~ s/^\Q$subdir\E//)) {
|
2008-10-23 22:29:50 +02:00
|
|
|
error sprintf(gettext("you are not allowed to change %s"), $file);
|
2008-10-23 02:52:34 +02:00
|
|
|
}
|
|
|
|
|
2008-10-23 22:29:50 +02:00
|
|
|
my $action;
|
|
|
|
my $mode;
|
|
|
|
if ($detail->{'status'} =~ /^[M]+\d*$/) {
|
|
|
|
$action="change";
|
|
|
|
$mode=$detail->{'mode_to'};
|
2008-10-23 02:52:34 +02:00
|
|
|
}
|
2008-10-23 22:29:50 +02:00
|
|
|
elsif ($detail->{'status'} =~ /^[AM]+\d*$/) {
|
|
|
|
$action="add";
|
|
|
|
$mode=$detail->{'mode_to'};
|
2008-10-23 02:52:34 +02:00
|
|
|
}
|
2008-10-23 22:29:50 +02:00
|
|
|
elsif ($detail->{'status'} =~ /^[DAM]+\d*/) {
|
|
|
|
$action="remove";
|
|
|
|
$mode=$detail->{'mode_from'};
|
2008-10-23 02:52:34 +02:00
|
|
|
}
|
|
|
|
else {
|
2008-10-23 22:29:50 +02:00
|
|
|
error "unknown status ".$detail->{'status'};
|
2008-10-23 02:52:34 +02:00
|
|
|
}
|
2008-10-23 22:29:50 +02:00
|
|
|
|
|
|
|
# test that the file mode is ok
|
|
|
|
if ($mode !~ /^100[64][64][64]$/) {
|
|
|
|
error sprintf(gettext("you cannot act on a file with mode %s"), $mode);
|
|
|
|
}
|
|
|
|
if ($action eq "change") {
|
|
|
|
if ($detail->{'mode_from'} ne $detail->{'mode_to'}) {
|
|
|
|
error gettext("you are not allowed to change file modes");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
push @rets, {
|
|
|
|
file => $file,
|
|
|
|
action => $action,
|
|
|
|
};
|
2008-10-23 02:52:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-10-23 22:29:50 +02:00
|
|
|
return @rets;
|
2008-10-23 02:52:34 +02:00
|
|
|
} #}}}
|
|
|
|
|
2006-06-02 07:32:20 +02:00
|
|
|
1
|