# Copyright (C) 2011 Alex Schroeder # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . use strict; use v5.10; =head1 NAME git - An Oddmuse module to save all changes made into a git repository. =head1 INSTALLATION This module is easily installable; move this file into the B subdirectory for your data directory (C<$DataDir>). =cut =head1 CONFIGURATION Set these variables in the B file within your data directory. =head2 $GitBinary Default: C The fully qualified name for the binary to run. Your PATH will not be searched. =head2 $GitRepo Default: C<$DataDir/git> The directory in which the repository resides. If it doesn't exist, Oddmuse will create it for you. =head2 $GitMail Default: C The email address used to identify users in git. =head2 $GitDebug Default: 0 If set, we capture the output of the git command and store it in $GitResult. This is useful when writing tests. =head2 $GitResult If $GitDebug is set, this variable holds STDOUT of the git command. =cut use Cwd; use File::Temp (); AddModuleDescription('git.pl', 'Git Extension'); our ($q, %Page, %Action, %IndexHash, @IndexList, @MyInitVariables, @MyMaintenance, $DataDir); our ($GitBinary, $GitRepo, $GitMail, $GitPageFile, $GitDebug, $GitResult); $GitBinary = 'git'; $GitMail = 'unknown@oddmuse.org'; $GitPageFile = 0; push(@MyInitVariables, \&GitInitVariables); sub GitRun { my $oldDir = cwd; my $exitStatus; # warn join(' ', $GitBinary, @_) . "\n"; ChangeDir($GitRepo); if ($GitDebug) { # TODO use ToString here # capture the output of the git comand in a temporary file my $fh = File::Temp->new(); open(my $oldout, ">&STDOUT") or die "Can't dup STDOUT: $!"; open(STDOUT, '>', $fh) or die "Can't redirect STDOUT: $!"; # run git in the work directory $exitStatus = system($GitBinary, @_); # read the temporary file with the output close($fh); open(STDOUT, ">&", $oldout) or die "Can't dup \$oldout: $!"; open(my $F, '<', $fh) or die "Can't open temp file for reading: $!"; local $/ = undef; # Read complete files $GitResult = <$F>; close($F); } else { $exitStatus = system($GitBinary, @_); } ChangeDir($oldDir); return $exitStatus; } sub GitInitVariables { $GitRepo = $DataDir . '/git'; } sub GitInitRepository { return if IsDir("$GitRepo/.git"); my $exception = shift; CreateDir($GitRepo); GitRun(qw(init --quiet)); # Add legacy pages: If you installed this extension for an older # wiki, all the existing pages need to be added. We do this for all # the pages except for the one we just saved. That page will get a # better author and log message. foreach my $id (AllPagesList()) { next if $id eq $exception; OpenPage($id); WriteStringToFile("$GitRepo/$id", $GitPageFile ? EncodePage(%Page) : $Page{text}); GitRun(qw(add --), $id); } GitRun(qw(commit --quiet -m), 'initial import', "--author=Oddmuse <$GitMail>"); } *GitOldSave = \&Save; *Save = \&GitNewSave; sub GitNewSave { # Save is called within lock, with opened page. That's why we cannot # call GitInitRepository right away, because it opens all the legacy # pages to save them, too. We need to save first. GitOldSave(@_); # We also need to save all the data from the open page. my $message = $Page{summary}; $message =~ s/^\s+$//; $message ||= T('no summary available'); my $author = $Page{username} || T('Anonymous'); my $data = $GitPageFile ? EncodePage(%Page) : $Page{text}; my $id = shift; # GitInitRepository will try to add and commit all the pages already # in the wiki. These are assumed to be legacy pages. The page we # just saved, however, should not be committed as a legacy page # because legacy pages are committed with a default author and log # message! GitInitRepository($id); WriteStringToFile("$GitRepo/$id", $data); GitRun(qw(add --), $id); GitRun(qw(commit --quiet -m), $message, "--author=$author <$GitMail>", '--', $id); } *GitOldDeletePage = \&DeletePage; *DeletePage = \&GitNewDeletePage; sub GitNewDeletePage { my $error = GitOldDeletePage(@_); return $error if $error; GitInitRepository(); my ($id) = @_; GitRun(qw(rm --quiet --ignore-unmatch --), $id); my $message = T('page was marked for deletion'); my $author = T('Oddmuse'); GitRun(qw(commit --quiet -m), $message, "--author=$author <$GitMail>", '--', $id); return ''; # no error } push(@MyMaintenance, \&GitCleanup); $Action{git} = \&DoGitCleanup; sub DoGitCleanup { UserIsAdminOrError(); print GetHeader('', 'Git', ''); print $q->start_div({-class=>'content git'}); RequestLockOrError(); print $q->p(T('Main lock obtained.')), '

', T('Cleaning up git repository'); GitCleanup(); ReleaseLock(); print $q->p(T('Main lock released.')), $q->end_div(); PrintFooter(); } sub GitCleanup { if (IsDir($GitRepo)) { print $q->p('Git cleanup starting'); AllPagesList(); # delete all the files including all the files starting with a dot opendir(DIR, encode_utf8($GitRepo)) or ReportError("cannot open directory $GitRepo: $!"); foreach my $file (readdir(DIR)) { my $name = decode_utf8($file); next if $file eq '.git' or $file eq '.' or $file eq '..' or $IndexHash{$name}; print $q->p("Deleting left over file $name"); Unlink("$GitRepo/$file") or print $q->p("Cannot delete $GitRepo/$name: $!"); } closedir DIR; # write all the files again, just to be sure print $q->p('Rewriting all the files, just to be sure'); foreach my $id (@IndexList) { OpenPage($id); WriteStringToFile("$GitRepo/$id", $GitPageFile ? EncodePage(%Page) : $Page{text}); } # run git! # add any new files print $q->p('Adding new files, if any'); GitRun(qw(add -A)); # commit the new state print $q->p('Committing changes, if any'); my $exitStatus = GitRun(qw(commit --quiet -m), 'maintenance job', "--author=Oddmuse <$GitMail>"); print $q->p('git commit finished with ' . $exitStatus . ' exit status.'); print $q->p('Git done'); } }