From e17c8388e53edae27a7e7817f0370aa5d9a7a28d Mon Sep 17 00:00:00 2001 From: Arthur Axel 'fREW' Schmidt Date: Sat, 23 Mar 2013 10:46:20 -0500 Subject: [PATCH] initial commit --- .gitignore | 3 + .travis.yml | 19 ++++ cpanfile | 8 ++ dist.ini | 16 +++ lib/Git/Validate.pm | 126 +++++++++++++++++++++++ lib/Git/Validate/Error/LongLine.pm | 21 ++++ lib/Git/Validate/Error/MissingBreak.pm | 17 ++++ lib/Git/Validate/Errors.pm | 26 +++++ lib/Git/Validate/HasLine.pm | 15 +++ t/basic.t | 136 +++++++++++++++++++++++++ 10 files changed, 387 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 cpanfile create mode 100644 dist.ini create mode 100644 lib/Git/Validate.pm create mode 100644 lib/Git/Validate/Error/LongLine.pm create mode 100644 lib/Git/Validate/Error/MissingBreak.pm create mode 100644 lib/Git/Validate/Errors.pm create mode 100644 lib/Git/Validate/HasLine.pm create mode 100644 t/basic.t diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a61eb5b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.gz +.build +Git-Validate-* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..fec4a4f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: perl +perl: + - "5.18" + - "5.16" + - "5.14" + - "5.12" + - "5.10" + - "5.8" + +install: + - export RELEASE_TESTING=1 AUTOMATED_TESTING=1 AUTHOR_TESTING=1 HARNESS_OPTIONS=j10:c HARNESS_TIMER=1 + - cpanm --quiet --notest Devel::Cover::Report::Coveralls SQL::Translator + - cpanm --quiet --notest --installdeps . + +script: + - PERL5OPT=-MDevel::Cover=-coverage,statement,branch,condition,path,subroutine prove -lrsv t + - cover +after_success: + - cover -report coveralls diff --git a/cpanfile b/cpanfile new file mode 100644 index 0000000..6b96263 --- /dev/null +++ b/cpanfile @@ -0,0 +1,8 @@ +requires 'Moo' => 1.003001; +requires 'namespace::clean' => 0.23; +requires 'IPC::System::Simple' => 1.25; +requires 'Module::Runtime' => 0.013; + +on test => sub { + requires 'Test::More' => 1.001002; +}; diff --git a/dist.ini b/dist.ini new file mode 100644 index 0000000..e7f3d62 --- /dev/null +++ b/dist.ini @@ -0,0 +1,16 @@ +name = Git-Validate +author = Arthur Axel "fREW" Schmidt +license = Perl_5 +copyright_holder = Arthur Axel "fREW" Schmidt +version = 0.001000 + +[NextRelease] +[@Git] +[@Basic] +[GithubMeta] +[MetaJSON] +[PickyPodWeaver] +[PkgVersion] +[ReadmeFromPod] +[PodSyntaxTests] +[Prereqs::FromCPANfile] diff --git a/lib/Git/Validate.pm b/lib/Git/Validate.pm new file mode 100644 index 0000000..9d757da --- /dev/null +++ b/lib/Git/Validate.pm @@ -0,0 +1,126 @@ +package Git::Validate; + +# ABSTRACT: Validate Git Commit Messages + +use IPC::System::Simple 'capture'; +use Module::Runtime 'use_module'; + +use Moo; + +use namespace::clean; + +sub _get_commit_message { capture(qw(git log -1 --pretty=%B), $_[1]) } + +sub validate_commit { + my ($self, $commit) = @_; + + $self->validate_message( + $self->_get_commit_message($commit) + ) +} + +sub validate_message { + my ($self, $message) = @_; + + my @lines = split /\n/, $message; + + my @e; + + # check tense? + # check initial case? + push @e, use_module('Git::Validate::Error::LongLine') + ->new( line => $lines[0], max_length => 50 ) + if length $lines[0] > 50; + + push @e, use_module('Git::Validate::Error::MissingBreak') + ->new( line => $lines[1] ) + if $lines[1]; + + my $i = 2; + for my $l (@lines[2..$#lines]) { + $i++; + push @e, use_module('Git::Validate::Error::LongLine') + ->new( line => $l, line_number => $i ) + if $l =~ m/^\S/ && length $l > 72; + } + + use_module('Git::Validate::Errors')->new(errors => \@e) +} + +1; + +__END__ + +=pod + +=head1 SYNOPSIS + + use Git::Validate; + + my $validator = Git::Validate->new; + my $errors = $validator->validate_commit('HEAD'); + + die "$errors\n" if $errors; + + +Or if you want to be all classy and modern: + + for $e (@{$errors->errors}) { + warn $e->line . " longer than " . $e->max_length . " characters!\n" + if $e->isa('Git::Validate::Error::LongLine') + } + +=head1 DESCRIPTION + +While many users apparently don't know it, there are actual correct ways to +write a C commit message. For a good summary of why, read +L<< this blog post | http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html >>. + +This module does it's best to automatically check commit messages against The +Rules. The current automatic checks are: + +=over 2 + +=item * First line should be 50 or fewer characters + +=item * Second line should be blank + +=item * Third and following lines should be less than 72 characters + +=back + +=head1 METHODS + +=head2 C + + my $errors = $validator->validate_commit('HEAD'); + +returns L for a given commit + +=head2 C + + my $errors = $validator->validate_message($commit_message); + +returns L for a given message + +=head1 ERRORS + +The object containing errors conveniently C and C. If +you need more information, please please please don't try to parse the returned +strings. Instead, note that the errors returned are a set of objects. These +are the objects you can check for: + +=over 2 + +=item * C + +=item * C + +=back + +The objects can be accessed with the C method, which returns an +arrayref. The objects have C and C methods. +The C<::LongLine> objects have a C method as well. + +=cut + diff --git a/lib/Git/Validate/Error/LongLine.pm b/lib/Git/Validate/Error/LongLine.pm new file mode 100644 index 0000000..0e92213 --- /dev/null +++ b/lib/Git/Validate/Error/LongLine.pm @@ -0,0 +1,21 @@ +package Git::Validate::Error::LongLine; + +use Moo; + +use overload q("") => '_stringify'; + +with 'Git::Validate::HasLine'; + +has '+line_number' => ( default => 1 ); + +has max_length => ( + is => 'ro', + default => 72, +); + +sub _stringify { + sprintf 'line %d is too long, max of %d chars, instead it is %d', + $_[0]->line_number, $_[0]->max_length, length $_[0]->line +} + +1; diff --git a/lib/Git/Validate/Error/MissingBreak.pm b/lib/Git/Validate/Error/MissingBreak.pm new file mode 100644 index 0000000..41c461e --- /dev/null +++ b/lib/Git/Validate/Error/MissingBreak.pm @@ -0,0 +1,17 @@ +package Git::Validate::Error::MissingBreak; + +use Moo; + +use overload q("") => '_stringify'; + +with 'Git::Validate::HasLine'; + +has '+line_number' => ( default => 2 ); + +sub _stringify { + sprintf 'line %d should be blank, instead it was "%s"', + $_[0]->line_number, $_[0]->line +} + +1; + diff --git a/lib/Git/Validate/Errors.pm b/lib/Git/Validate/Errors.pm new file mode 100644 index 0000000..452969c --- /dev/null +++ b/lib/Git/Validate/Errors.pm @@ -0,0 +1,26 @@ +package Git::Validate::Errors; + +use Moo; +use overload + q("") => '_stringify', + 'bool' => '_boolify', +; + +has errors => ( + is => 'ro', + required => 1, + isa => sub { + die 'errors must be an arrayref' + unless ref $_[0] && ref $_[0] eq 'ARRAY' + }, +); + +sub _stringify { + return "" . $_[0]->errors->[0] if @{$_[0]->errors} == 1; + + join "\n", map " * $_", @{$_[0]->errors} +} + +sub _boolify { scalar @{$_[0]->errors} } + +1; diff --git a/lib/Git/Validate/HasLine.pm b/lib/Git/Validate/HasLine.pm new file mode 100644 index 0000000..983e886 --- /dev/null +++ b/lib/Git/Validate/HasLine.pm @@ -0,0 +1,15 @@ +package Git::Validate::HasLine; + +use Moo::Role; + +has line => ( + is => 'ro', + required => 1, +); + +has line_number => ( + is => 'ro', + required => 1, +); + +1; diff --git a/t/basic.t b/t/basic.t new file mode 100644 index 0000000..d238a02 --- /dev/null +++ b/t/basic.t @@ -0,0 +1,136 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +use Test::More; + +use Git::Validate; + +my $v = Git::Validate->new; + +{ +my $m =<<'MESSAGE'; +Fix computer +MESSAGE +ok(!@{$v->validate_message($m)->errors}, 'no errors for short simple message'); +} + +{ +my $m =<<'MESSAGE'; +Test Message for thing abcdefghijklmnopqrstuvwxyzt +MESSAGE +ok(!@{$v->validate_message($m)->errors}, 'no errors for exactly 50 char message'); +} + +{ +my $m =<<'MESSAGE'; +Test Message for thing abcdefghijklmnopqrstuvwxyzt + +This was a hard test to write, but we got it done. I'm glad we're nearly +still friends! +MESSAGE +ok( + !@{$v->validate_message($m)->errors}, + 'no errors for exactly 50 char message and exactly 72 char body', +); +} + +{ +my $m =<<'MESSAGE'; +Test Message for thing abcdefghijklmnopqrstuvwxyzt + +This was a hard test to write, but we got it done. I'm glad we're nearly +still friends! + + INDENTED CODE IS FOR LITERALS AND THUS CAN BE LONGER THAN 72 CHARACTERS WOO WOO WOO +MESSAGE +ok( + !@{$v->validate_message($m)->errors}, + 'no errors for exactly 50 char message and exactly 72 char body and long literal', +); +} + +{ +local $TODO = 'check tense'; +my $m =<<'MESSAGE'; +Fixed computer +MESSAGE +ok(@{$v->validate_message($m)->errors}, 'no errors for short simple message'); + +$m =<<'MESSAGE'; +Fixes computer +MESSAGE +ok(@{$v->validate_message($m)->errors}, 'no errors for short simple message'); + +$m =<<'MESSAGE'; +Fixing computer +MESSAGE +ok(@{$v->validate_message($m)->errors}, 'no errors for short simple message'); +} + +{ +my $m =<<'MESSAGE'; +Fix bug in some dumb thing; also do some other thing do show a long line +MESSAGE +my @e = @{$v->validate_message($m)->errors}; + +is(@e, 1, 'got an error due to long first line'); +ok($e[0]->isa('Git::Validate::Error::LongLine'), 'correct error obj'); +is($e[0]->line_number, 1, 'correct error line'); +is('' . $e[0], 'line 1 is too long, max of 50 chars, instead it is 72', 'correct error line'); +} + +{ +my $m =<<'MESSAGE'; +Fix bug in some dumb thing +also do some other thing do show a non-blank line +MESSAGE +my @e = @{$v->validate_message($m)->errors}; + +is(@e, 1, 'got an error due to non-blank second line'); +ok($e[0]->isa('Git::Validate::Error::MissingBreak'), 'correct error obj'); +is($e[0]->line_number, 2, 'correct error line'); +is('' . $e[0], 'line 2 should be blank, instead it was "also do some other thing do show a non-blank line"', 'correct error line'); +} + +{ +my $m =<<'MESSAGE'; +Fix bug in some dumb thing + +Get too wordy and write a much too long body woo woo woo woo woo woo wo woo +MESSAGE +my @e = @{$v->validate_message($m)->errors}; + +is(@e, 1, 'got an error due to too long body line'); +ok($e[0]->isa('Git::Validate::Error::LongLine'), 'correct error obj'); +is($e[0]->line_number, 3, 'correct error line'); +is('' . $e[0], 'line 3 is too long, max of 72 chars, instead it is 75', 'correct error line'); +} + +{ # all together now +my $m =<<'MESSAGE'; +Fix bug in some dumb thing; also do some other thing do show a long line +also do some other thing do show a non-blank line +Get too wordy and write a much too long body woo woo woo woo woo woo wo woo +MESSAGE +my $e = $v->validate_message($m); +my @e = @{$e->errors}; + +is(@e, 3, 'got all the errors'); + +ok($e[0]->isa('Git::Validate::Error::LongLine'), 'correct error obj'); +is($e[0]->line_number, 1, 'correct error line'); + +ok($e[1]->isa('Git::Validate::Error::MissingBreak'), 'correct error obj'); +is($e[1]->line_number, 2, 'correct error line'); + +ok($e[2]->isa('Git::Validate::Error::LongLine'), 'correct error obj'); +is($e[2]->line_number, 3, 'correct error line'); +is($e . "\n", <<'MSG', 'correct error line'); + * line 1 is too long, max of 50 chars, instead it is 72 + * line 2 should be blank, instead it was "also do some other thing do show a non-blank line" + * line 3 is too long, max of 72 chars, instead it is 75 +MSG +} +done_testing;