123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- # Copyright (C) 2011 Alex Schroeder <alex@gnu.org>
- # 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 <http://www.gnu.org/licenses/>.
- 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<modules>
- subdirectory for your data directory (C<$DataDir>).
- =cut
- =head1 CONFIGURATION
- Set these variables in the B<config> file within your data directory.
- =head2 $GitBinary
- Default: C<git>
- 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<unknown@oddmuse.org>
- 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.')), '<p>', 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 ReportError("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');
- }
- }
|