Skip to content

Commit

Permalink
Merge branch 'gh7_ignore_whitespace_option'
Browse files Browse the repository at this point in the history
  • Loading branch information
guillaumeaubert committed May 24, 2015
2 parents f82d95f + c7e2876 commit fb3d19c
Show file tree
Hide file tree
Showing 5 changed files with 336 additions and 37 deletions.
1 change: 1 addition & 0 deletions MANIFEST
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ t/05-Cache/15-get_repository.t
t/05-Cache/20-set_blame_lines.t
t/05-Cache/25-get_blame_lines.t
t/10-blame.t
t/10-blame-ignore_whitespace.t
t/Line/00-load.t
t/Line/10-new.t
t/Line/20-get_commit_attributes.t
Expand Down
20 changes: 17 additions & 3 deletions lib/Git/Repository/Plugin/Blame.pm
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,15 @@ Arguments:
=over 4
=item * use_cache (default: 0)
=item * use_cache I<(default: 0)>
Cache the git blame output.
=item * ignore_whitespace I<(default: 0)>
Ignore whitespace when comparing the parent's version and the child's to find
where the lines came from.
=back
=cut
Expand All @@ -73,6 +78,7 @@ sub blame
{
my ( $repository, $file, %args ) = @_;
my $use_cache = delete( $args{'use_cache'} ) || 0;
my $ignore_whitespace = delete( $args{'ignore_whitespace'} ) || 0;
croak 'The following arguments are not valid: ' . join( ', ' , keys %args )
if scalar( keys %args ) != 0;

Expand All @@ -81,7 +87,13 @@ sub blame
if ( $use_cache )
{
my $class = Class::Load::load_class( 'Git::Repository::Plugin::Blame::Cache' );
$cache = $class->new( repository => $repository->work_tree() );
$cache = $class->new(
repository => $repository->work_tree(),
blame_args =>
{
ignore_whitespace => $ignore_whitespace,
},
);
croak 'Failed to initialize cache for repository ' . $repository->work_tree()
if !defined( $cache );

Expand All @@ -91,7 +103,9 @@ sub blame
}

# Run the command.
my $command = $repository->command( 'blame', '--porcelain', $file );
my @commandline_options = ( '--porcelain' );
push( @commandline_options, '-w' ) if $ignore_whitespace;
my $command = $repository->command( 'blame', @commandline_options, $file );
my @output = $command->final_output();

# Parse the output.
Expand Down
40 changes: 34 additions & 6 deletions lib/Git/Repository/Plugin/Blame/Cache.pm
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,24 @@ Return a cache object for the specified repository.
my $cache = Git::Repository::Plugin::Blame::Cache->new(
repository => $repository,
options => $options,
);
Arguments:
=over 4
=item * repository (mandatory)
=item * repository I<(mandatory)>
A unique way to identify a repository. Typically, the root path of the
repository.
=item * blame_args I<(optional)>
A hashref of arguments used to generate the C<git blame> output, if applicable.
This avoids caching the same output for C<git blame> and C<git blame -w>, for
example.
=back
=cut
Expand All @@ -79,24 +86,45 @@ sub new
{
my ( $class, %args ) = @_;
my $repository = delete( $args{'repository'} );
my $blame_args = delete( $args{'blame_args'} ) || {};
croak 'The following arguments are not valid: ' . join( ',', keys %args )
if scalar( keys %args ) != 0;

# Verify mandatory arguments.
croak 'The "repository" argument is mandatory'
if !defined( $repository ) || $repository eq '';

if ( !defined( $CACHE->{ $repository } ) )
# Serialize the options passed, to hold a separate cache entry for each set
# of options.
my $serialized_blame_args = '';
if ( scalar( keys %$blame_args ) != 0 )
{
my @blame_args = ();
foreach my $blame_arg ( sort keys %$blame_args )
{
my $value = defined( $blame_args->{ $blame_arg } )
? $blame_args->{ $blame_arg }
: '';
push( @blame_args, "$blame_arg=$value" );
}
$serialized_blame_args = join( ',', @blame_args );
}

# If the cache element does not exist, create it.
if ( !defined( $CACHE->{ $repository }->{ $serialized_blame_args } ) )
{
$CACHE->{ $repository } = bless(
$CACHE->{ $repository }->{ $serialized_blame_args } = bless(
{
repository => $repository,
files => {},
repository => $repository,
files => {},
serialized_blame_args => $serialized_blame_args,
},
$class,
);
}

return $CACHE->{ $repository };
# Return the corresponding cache element.
return $CACHE->{ $repository }->{ $serialized_blame_args };
}


Expand Down
170 changes: 170 additions & 0 deletions t/10-blame-ignore_whitespace.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
#!perl

# Note: cannot use -T here, Git::Repository uses environment variables directly.

use strict;
use warnings;

use Git::Repository ( 'Blame', 'Log' );
use Git::Repository::Plugin::Blame::Cache;
use Test::Deep;
use Test::Exception;
use Test::FailWarnings -allow_deps => 1;
use Test::Git;
use Test::More;
use Test::Type;


# Check there is a git binary available, or skip all.
has_git( '1.5.0' );

# Declare the list of tests to run.
my $tests =
[
{
name => 'Test blame without ignore_whitespace argument.',
options => {},
expected => [ 'Author1', 'Author2', 'Author1' ],
},
{
name => 'Test blame with ignore_whitespace=0.',
options => { ignore_whitespace => 0 },
expected => [ 'Author1', 'Author2', 'Author1' ],
},
{
name => 'Test blame with ignore_whitespace=1.',
options => { ignore_whitespace => 1 },
expected => [ 'Author1', 'Author1', 'Author1' ],
},
];

plan( tests => 3 + scalar( @$tests ) );

# Create a new, empty repository in a temporary location and return
# a Git::Repository object.
my $repository = Test::Git::test_repository();

my $work_tree = $repository->work_tree();
ok(
defined( $work_tree ) && -d $work_tree,
'Find the work tree for the temporary test repository.',
);

# Name of the test file to use.
my $test_file = $work_tree . '/README';

subtest(
'Commit new test file.',
sub
{
plan( tests => 3 );

# Set up the first author.
local $ENV{'GIT_AUTHOR_NAME'} = 'Author1';
local $ENV{'GIT_AUTHOR_EMAIL'} = '[email protected]';
local $ENV{'GIT_COMMITTER_NAME'} = 'Author1';
local $ENV{'GIT_COMMITTER_EMAIL'} = '[email protected]';

# Create a new file.
ok(
open( my $fh, '>', $test_file ),
'Create test file.'
) || diag( "Failed to open $test_file for writing: $!" );
print $fh "Test 1.\n";
print $fh "Test 2.\n";
print $fh "Test 3.\n";
close( $fh );

# Add the file to git.
lives_ok(
sub
{
$repository->run( 'add', $test_file );
},
'Add test file to the Git index.',
);
lives_ok(
sub
{
$repository->run( 'commit', '-m "First commit."' );
},
'Commit to Git.',
);
}
);

subtest(
'Edit test file with only whitespace modifications.',
sub
{
plan( tests => 2 );

# Switch to editing the file with a different author.
local $ENV{'GIT_AUTHOR_NAME'} = 'Author2';
local $ENV{'GIT_AUTHOR_EMAIL'} = '[email protected]';
local $ENV{'GIT_COMMITTER_NAME'} = 'Author2';
local $ENV{'GIT_COMMITTER_EMAIL'} = '[email protected]';

# Modify the file.
ok(
open( my $fh, '>', $test_file ),
'Modify test file.'
) || diag( "Failed to open $test_file for writing: $!" );
print $fh "Test 1.\n";
print $fh "Test 2. \n";
print $fh "Test 3.\n";
close( $fh );

# Commit the changes to git.
lives_ok(
sub
{
$repository->run( 'commit', '-m "Second commit."', '-a' );
},
'Commit to Git.',
);
}
);

foreach my $test ( @$tests )
{
subtest(
$test->{'name'},
sub
{
plan( tests => 4 );

# Get the blame information.
my $blame_lines;
lives_ok(
sub
{
$blame_lines = $repository->blame(
$test_file,
%{ $test->{'options'} },
);
},
'Retrieve git blame information.',
);

is(
scalar( @$blame_lines ),
3,
'Verify the number of lines with blame information',
);

ok(
defined(
my $authors = [ map { $_->get_commit_attributes()->{'author'} } @$blame_lines ]
),
'Prepare the list of authors found in the git blame output.',
);

is_deeply(
$authors,
$test->{'expected'},
'The reported authors match the expected results.',
) || diag( 'Found: ', explain( $authors ), 'Expected: ', explain( $test->{'expected'} ) );
}
);
}
Loading

0 comments on commit fb3d19c

Please sign in to comment.