From 450a3ec1416c3afdabe414d56ba335a4de566f60 Mon Sep 17 00:00:00 2001 From: GeorgeClark Date: Fri, 1 Aug 2014 01:05:00 +0000 Subject: [PATCH] Item11267: Changes to support git submodules The "working directory" is different when a git hook is called from a submodule, so the lib path for the hooks needs to be adjusted. Also, the githooks are installed into two different locations: Normal repo: .git/hooks submodule repo: .git/modules//hooks Conflicts: core/pseudo-install.pl core/tools/develop/githooks/commit-msg core/tools/develop/githooks/pre-commit git-svn-id: http://svn.foswiki.org/branches/Release01x01@17832 0b4bb1d4-4e5a-0410-9cc4-b2b747904278 --- core/pseudo-install.pl | 59 ++++- core/tools/develop/githooks/commit-msg | 154 ++++++++++++ core/tools/develop/githooks/pre-commit | 312 +++++++++++++++++++++++++ 3 files changed, 523 insertions(+), 2 deletions(-) create mode 100755 core/tools/develop/githooks/commit-msg create mode 100755 core/tools/develop/githooks/pre-commit diff --git a/core/pseudo-install.pl b/core/pseudo-install.pl index 6bd9e918e1..32a7fc87df 100755 --- a/core/pseudo-install.pl +++ b/core/pseudo-install.pl @@ -324,7 +324,7 @@ sub error { sub trace { - #warn "...",@_,"\n"; + #warn "...", @_, "\n"; return; } @@ -441,9 +441,10 @@ sub installModuleByName { ); $libDir = 'TWiki'; } - if ( -e $manifest ) { + if ( $manifest && -e $manifest ) { installFromMANIFEST( $module, $moduleDir, $manifest, $ignoreBlock ); update_gitignore_file($moduleDir); + update_githooks_dir( $moduleDir, $module ); } else { $libDir = undef; @@ -1477,6 +1478,60 @@ sub update_gitignore_file { return; } +# install the githooks. If called with a module name (ie. "CommentPlugin") +# then we might be in a .git "superproject" structure, so look for a .git/modules/$module/hooks +# directory. otherwise a final call at the end will install into the primary .git/hooks location + +sub update_githooks_dir { + my ( $moduleDir, $module ) = @_; + $module ||= ''; + use Cwd; + + my $hooks_src = File::Spec->catdir( 'tools', 'develop', 'githooks' ); + + # Check for .git directories, and copy in hooks if needed + foreach my $gitdir ( '.', '..' ) { + my $hooks_tgt = File::Spec->catdir( $gitdir, '.git', 'hooks' ); + my $target_dir = + File::Spec->catdir( $moduleDir, $gitdir, '.git', 'hooks' ); + my $gitmodule_target_dir = + File::Spec->catdir( $gitdir, '.git', 'modules', $module, 'hooks' ); + my $gitmodule_hooks_tgt = + File::Spec->catdir( $gitdir, '.git', 'modules', $module, 'hooks' ); + + foreach my $hook ( + qw( applypatch-msg commit-msg post-commit post-update pre-applypatch pre-commit pre-rebase prepare-commit-msg post-receive update) + ) + { + next unless ( -f File::Spec->catfile( $hooks_src, $hook ) ); + + if ($module) { + if ( -d $gitmodule_target_dir ) { + unlink File::Spec->catfile( $gitmodule_target_dir, $hook ) + if ( + -e File::Spec->catfile( $gitmodule_target_dir, $hook ) + ); + linkOrCopy '.', + File::Spec->catfile( $hooks_src, $hook ), + File::Spec->catfile( $gitmodule_hooks_tgt, $hook ), + $CAN_LINK; + } + } + else { + if ( -d $target_dir ) { + unlink _cleanPath( + File::Spec->catfile( $target_dir, $hook ) ) + if ( -e File::Spec->catfile( $target_dir, $hook ) ); + linkOrCopy $moduleDir, + File::Spec->catfile( $hooks_src, $hook ), + File::Spec->catfile( $hooks_tgt, $hook ), + $CAN_LINK; + } + } + } + } +} + init(); exec_opts(); init_config(); diff --git a/core/tools/develop/githooks/commit-msg b/core/tools/develop/githooks/commit-msg new file mode 100755 index 0000000000..3ca01311a9 --- /dev/null +++ b/core/tools/develop/githooks/commit-msg @@ -0,0 +1,154 @@ +#!/usr/bin/perl +# See bottom of file for license and copyright information + +use strict; +use warnings; + +BEGIN { + # Look in the current and parent directories for libraries + # With git submodules, the current directory is one lib lower than + # with a normal git repo during the commit + + if ( -d './core' ) { + unshift @INC, './core/lib'; # Pick up foswiki libs + } + elsif ( -d '../core' ) { + unshift @INC, '../core/lib'; # Pick up foswiki libs + } + else { + print STDERR + "Unable to find the location of the foswiki core directory\n"; + print STDERR +"Be sure that foswiki core has been installed before attempting to commit changes\n"; + die "The pre-commit-msg exit won't work - commit aborted"; + } + if ( -d './BuildContrib' ) { + unshift @INC, + './BuildContrib/lib'; # Pick up BuildContrib version of PerlTidy + } + elsif ( -d '../BuildContrib' ) { + unshift @INC, + '../BuildContrib/lib'; # Pick up BuildContrib version of PerlTidy + } + else { + print STDERR "Unable to find the location of BuildContrib\n"; + print STDERR +"Be sure that BuildContrib has been pseudo-installed before attempting to commit changes\n"; + die "The pre-commit-msg exit won't work - commit aborted"; + } +} + +use LWP::Simple; + +# COMMIT-MSG CLIENT HOOK for Foswiki git +# +# The commit-msg hook tests that the message identifies one or more +# tasks, and that each task is in a state to accept checkins. +# +# STDERR ends up on the users' terminal + +# These are not used here except to keep the error message in sync with pre-commit +my $WINDOW_DAYS = 3; # window for date in %META +my $WINDOW = $WINDOW_DAYS * 24 * 60 * 60; + +my $failmsg = ''; +my $logmsg; + +{ + local $/ = undef; + open my $fh, "<", $ARGV[0] + or die "could not open $ARGV[0]: $!"; + $logmsg = <$fh>; + close $fh; +}; + +fail("No Bug item in log message\n") unless ( $logmsg =~ /^Item\d+\s*:/ ); + +my @items; +$logmsg =~ s/\b(Item\d+)\s*:/push(@items, $1); '';/gem; +foreach my $item (@items) { + my $url = "http://foswiki.org/Tasks/ItemStatusQuery?item=$item;skin=text"; + my $state = get $url; + + unless ( defined $state ) { + $failmsg .= +"ERROR: GET on Tasks.ItemStatusQuery failed. Check http://foswiki.org/Tasks/ItemStatusQuery\n"; + fail($failmsg); + } + + $failmsg .= "$item does not exist\n" unless $state; + + if ( $state =~ + /^(Waiting for Release|Closed|No Action Required|Proposal Required)$/ ) + { + $failmsg .= "$item is in $state state; cannot check in\n"; + } + +} + +fail($failmsg) if $failmsg; + +exit 0; + +# PLEASE keep this message in sync with +# http://foswiki.org/Development/SvnRepository#RulesForCheckins +sub fail { + my $message = shift; + print STDERR <<"EOF"; +-------------------------------------------------------------- +Illegal checkin to Foswiki git repo found in pre-commit: + +======= +$message +======= + +http://foswiki.org/Development/GitRepository#RulesForCheckins +Rules - files being checked in must: +1. Have a comment... +2. ...with relevant ItemNNN task topics in the first line, e.g. + +Item12345: Item12346: fixed foo, updated release notes + +3. Refer to ItemNNN task topics which are open at the time of + checkin, i.e. *not* one of: Closed, Waiting For Release, + No Action or Proposal Required + +4. .pl and .pm files must be "tidied" if the TIDY control file + in the root of the extension calls for it, see: + http://foswiki.org/Development/TIDY + +5. .txt files in web directories must have META:TOPICINFO with + the author "ProjectContributor", a version of 1 and a date + within $WINDOW_DAYS days of the checkin. Any FILEATTACHMENTs must + has the "ProjectContributor" author, a version of 1 and a date + with $WINDOW_DAYS days of the checkin. + +Getting rejected commits with perltidy? We are checking using +version $Perl::Tidy::VERSION +See http://foswiki.org/Development/PerlTidy#Versions +------------TRUNCATED COMMIT MESSAGE FOLLOWS------------------------ +EOF + $logmsg =~ s/^# Untracked files:.*//ms; + print STDERR $logmsg; + exit 1; +} + +__END__ +Foswiki - The Free and Open Source Wiki, http://foswiki.org/ + +Copyright (C) 2014 Foswiki Contributors. Foswiki Contributors +are listed in the AUTHORS file in the root of this distribution. +NOTE: Please extend that file, not this notice. + +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 2 +of the License, or (at your option) any later version. For +more details read LICENSE in the root of this distribution. + +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. + +As per the GPL, removal of this notice is prohibited. + diff --git a/core/tools/develop/githooks/pre-commit b/core/tools/develop/githooks/pre-commit new file mode 100755 index 0000000000..7d9d5b978d --- /dev/null +++ b/core/tools/develop/githooks/pre-commit @@ -0,0 +1,312 @@ +#!/usr/bin/perl +# See bottom of file for license and copyright information + +use strict; +use warnings; + +# Pick up BuildContrib version of Perl::Tidy +# SMELL: This works because the pre-commit runs in the root of the checkout +# Assumes that BuildContrib is always available. + +use Text::Diff; +use File::Spec; + +BEGIN { + # Look in the current and parent directories for libraries + # With git submodules, the current directory is one lib lower than + # with a normal git repo during the commit + + if ( -d './core' ) { + unshift @INC, './core/lib'; # Pick up foswiki libs + } + elsif ( -d '../core' ) { + unshift @INC, '../core/lib'; # Pick up foswiki libs + } + else { + print STDERR + "Unable to find the location of the foswiki core directory\n"; + print STDERR +"Be sure that foswiki core has been installed before attempting to commit changes\n"; + die "The pre-commit-msg exit won't work - commit aborted"; + } + if ( -d './BuildContrib' ) { + unshift @INC, + './BuildContrib/lib'; # Pick up BuildContrib version of PerlTidy + } + elsif ( -d '../BuildContrib' ) { + unshift @INC, + '../BuildContrib/lib'; # Pick up BuildContrib version of PerlTidy + } + else { + print STDERR "Unable to find the location of BuildContrib\n"; + print STDERR +"Be sure that BuildContrib has been pseudo-installed before attempting to commit changes\n"; + die "The pre-commit-msg exit won't work - commit aborted"; + } +} + +# Require these at runtime because we need to use the modified libpath +require Foswiki::Attrs; +require Perl::Tidy; + +# PRE-COMMIT CLIENT HOOK for Foswiki git +# +# The pre-commit hook tests +# - that the all perl files are tidy. +# - All TOPICINFO have ProjectContributor, version 1, and recent timestamp +# +# STDERR ends up on the users' terminal + +my $WINDOW_DAYS = 3; # window for date in %META +my $WINDOW = $WINDOW_DAYS * 24 * 60 * 60; + +my $failmsg = ''; +my %tidyOption; + +# Verify that code is cleanly formatted, but only for files which are: +# - all .pl and .pm files +# - files in bin/ with the perl shebang +# - excluding anything in lib/CPAN + +my @files = split( "\n", `git diff-index --cached --name-only HEAD` ); +foreach my $file (@files) { + + # CompareRevisionsAddon has a crafted demo topic, don't + # force the date or revision in TOPICINFO. + next if $file =~ /CompareRevisionsAddOnDemoTopic.txt(?:,v)?$/; + next if $file =~ /MANIFEST|DEPENDENCIES|TIDY|Makefile/; + + if ( $file =~ /\.p[ml]$/ ) { + + # Not CPAN modules + next if $file =~ m#/lib/CPAN/lib/#; + } + elsif ( $file =~ +m/\.(?:js|css|html|gif|jpe?g|png|ps|psd|gz|zip|pot?|tmpl|svg|patch|json|spec)$/ + ) + { + next; + } + elsif ( $file =~ m#\.txt$# ) { + + # Not .txt in a web + next unless $file =~ m#/data/#; + } + elsif ( $file =~ /,v$/ ) { + print STDERR <; + close $fh; + + if ( $file =~ /\.txt$/ ) { + my @err; + checkTOPICINFO( $input[0], \@err ); + checkFILEATTACHMENT( \@input, \@err ); + $failmsg .= + "\nERROR: $file meta-data is incorrect; cannot check in:\n" + . join( "\n", @err ) . "\n" + if scalar(@err); + } + else { + check_perltidy( \@input, $file ); + } + } +} + +#print STDERR Data::Dumper::Dumper( \%tidyOption ); +fail($failmsg) if ($failmsg); + +exit 0; + +# ============== END OF MAIN =================== + +# PLEASE keep this message in sync with +# http://foswiki.org/Development/SvnRepository#RulesForCheckins + +sub fail { + my $message = shift; + print STDERR <<"EOF"; +-------------------------------------------------------------- +Illegal checkin to Foswiki git repo found in pre-commit: + +======= +$message +======= + +http://foswiki.org/Development/GitRepository#RulesForCheckins +Rules - files being checked in must: +1. Have a comment... +2. ...with relevant ItemNNN task topics in the first line, e.g. + +Item12345: Item12346: fixed foo, updated release notes + +3. Refer to ItemNNN task topics which are open at the time of + checkin, i.e. *not* one of: Closed, Waiting For Release, + No Action or Proposal Required + +4. .pl and .pm files must be "tidied" if the TIDY control file + in the root of the extension calls for it, see: + http://foswiki.org/Development/TIDY + +5. .txt files in web directories must have META:TOPICINFO with + the author "ProjectContributor", a version of 1 and a date + within $WINDOW_DAYS days of the checkin. Any FILEATTACHMENTs must + has the "ProjectContributor" author, a version of 1 and a date + with $WINDOW_DAYS days of the checkin. + +Getting rejected commits with perltidy? We are checking using +version $Perl::Tidy::VERSION +See http://foswiki.org/Development/PerlTidy#Versions +-------------------------------------------------------------- +EOF + exit 1; +} + +# Returns undef when file should be skipped, +# otherwise returns perltidy options to be used (can be empty for defaults) +sub getTidyOptions { + my $file = shift; + return $tidyOption{$file} if exists $tidyOption{$file}; + + my $tidyOptions = undef; # Defaults to skip + my ( $volume, $directory ) = File::Spec->splitpath($file); + + my @pathList; # Save examined hierarchy to update cache + my @path = File::Spec->splitdir($directory); + while ( defined pop @path ) { + my $path = File::Spec->catdir(@path); + $tidyOptions = $tidyOption{$path} and last if exists $tidyOption{$path}; + push @pathList, $path; # To update cache hierachy + my $tidyFile = File::Spec->catpath( $volume, $path, 'TIDY' ); + next unless ( -f $tidyFile ); + open( my $fh, '<', $tidyFile ) or die("Unable to open file"); + my @tidyOptions = <$fh>; + close $fh; + + if ( $? == 0 ) { # Found a TIDY file, check its content + $tidyOptions = ''; # Defaults to check + for (@tidyOptions) { + if (/^(?:perl\s+)OFF$/) { + $tidyOptions = undef; + last; + } + if (/^perl\s*(.*)$/) { + $tidyOptions = $1; + last; + } + } + last; + } + } + + # Update cache for the entire paths + for my $path (@pathList) { + $tidyOption{$path} = $tidyOptions; + } + + return $tidyOption{$file} = $tidyOptions; +} + +sub check_perltidy { + my ( $input, $file ) = @_; + + return + unless ( $file =~ /\.p[ml]$/ + || $input->[0] =~ m(^#!\s*/usr/bin/perl) ); + + my $tidyOptions = getTidyOptions($file); + return unless defined $tidyOptions; + + my @tidyed; + Perl::Tidy::perltidy( + source => $input, + destination => \@tidyed, + argv => $tidyOptions, + ); + my $diff = diff( $input, \@tidyed ); + $failmsg .= "\nERROR: $file is not tidy; cannot check in:\n$diff" + if $diff; +} + +# Return error message if TOPICINFO is bad per the rules. +sub checkTOPICINFO { + my ( $ti, $err ) = @_; + unless ( $ti =~ /^%META:TOPICINFO{(.*)}%$/ ) { + push( @$err, 'No TOPICINFO' ); + return; + } + my $attrs = new Foswiki::Attrs($1); + my $auth = $attrs->{author} || 'unknown user'; + push( @$err, + "TOPICINFO: wrong author '$auth', must be 'ProjectContributor'" ) + unless ( $auth eq 'ProjectContributor' ); + my $date = $attrs->{date} || 0; + my $t = time; + push( @$err, "TOPICINFO: date must be within $WINDOW seconds of $t" ) + unless $date =~ /^\d+$/ + && abs( $t - $date ) < $WINDOW; + my $ver = $attrs->{version} || 0; + push( @$err, "TOPICINFO: version must be 1" ) + unless $attrs->{version} eq '1'; +} + +sub checkFILEATTACHMENT { + my ( $lines, $err ) = @_; + foreach my $meta (@$lines) { + if ( $meta =~ /^%META:FILEATTACHMENT{(.*)}%/ ) { + my $attrs = new Foswiki::Attrs($1); + my $name = $attrs->{name} || ''; + if ($name) { + $name = " '$name'"; + } + else { + push( @$err, "FILEATTACHMENT has no name" ); + } + my $auth = $attrs->{user} || 'unknown user'; + push( @$err, +"FILEATTACHMENT$name wrong user '$auth', must be 'ProjectContributor'" + ) unless ( $auth eq 'ProjectContributor' ); + my $date = $attrs->{date} || 0; + my $t = time; + push( @$err, "date must be within $WINDOW seconds of $t" ) + unless $date =~ /^\d+$/ + && abs( $t - $date ) < $WINDOW; + my $ver = $attrs->{version} || 0; + push( @$err, "version must be 1" ) + unless $attrs->{version} eq '1'; + } + } + return $err; +} +__END__ +Foswiki - The Free and Open Source Wiki, http://foswiki.org/ + +Copyright (C) 2014 Foswiki Contributors. Foswiki Contributors +are listed in the AUTHORS file in the root of this distribution. +NOTE: Please extend that file, not this notice. + +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 2 +of the License, or (at your option) any later version. For +more details read LICENSE in the root of this distribution. + +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. + +As per the GPL, removal of this notice is prohibited. + +