Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Arthur Axel 'fREW' Schmidt committed Nov 9, 2013
0 parents commit e17c838
Show file tree
Hide file tree
Showing 10 changed files with 387 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.gz
.build
Git-Validate-*
19 changes: 19 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions cpanfile
Original file line number Diff line number Diff line change
@@ -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;
};
16 changes: 16 additions & 0 deletions dist.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name = Git-Validate
author = Arthur Axel "fREW" Schmidt <frioux+cpan@gmail.com>
license = Perl_5
copyright_holder = Arthur Axel "fREW" Schmidt
version = 0.001000

[NextRelease]
[@Git]
[@Basic]
[GithubMeta]
[MetaJSON]
[PickyPodWeaver]
[PkgVersion]
[ReadmeFromPod]
[PodSyntaxTests]
[Prereqs::FromCPANfile]
126 changes: 126 additions & 0 deletions lib/Git/Validate.pm
Original file line number Diff line number Diff line change
@@ -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<git> 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<validate_commit>
my $errors = $validator->validate_commit('HEAD');
returns L</ERRORS> for a given commit
=head2 C<validate_message>
my $errors = $validator->validate_message($commit_message);
returns L</ERRORS> for a given message
=head1 ERRORS
The object containing errors conveniently C<stringifies> and C<boolifies>. 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<Git::Validate::Error::LongLine>
=item * C<Git::Validate::Error::MissingBreak>
=back
The objects can be accessed with the C<errors> method, which returns an
arrayref. The objects have C<line> and C<line_number> methods.
The C<::LongLine> objects have a C<max_length> method as well.
=cut
21 changes: 21 additions & 0 deletions lib/Git/Validate/Error/LongLine.pm
Original file line number Diff line number Diff line change
@@ -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;
17 changes: 17 additions & 0 deletions lib/Git/Validate/Error/MissingBreak.pm
Original file line number Diff line number Diff line change
@@ -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;

26 changes: 26 additions & 0 deletions lib/Git/Validate/Errors.pm
Original file line number Diff line number Diff line change
@@ -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;
15 changes: 15 additions & 0 deletions lib/Git/Validate/HasLine.pm
Original file line number Diff line number Diff line change
@@ -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;
136 changes: 136 additions & 0 deletions t/basic.t
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit e17c838

Please sign in to comment.