#!/usr/bin/perl -w # Copyright 2011 © Jon Dowland # Licensed under the GNU GPL version 2 or higher. use strict; use Config::IniFiles; use File::Spec::Functions qw/catfile splitpath/; use Cwd; use Getopt::Long; use File::Find; use 5.010; my $verbose; GetOptions ( 'verbose' => \$verbose, 'help' => sub { say "usage: mr-lint [ --verbose ] [ repository ]\n\n". "If no repository is stated, mr-lint will run for ". "every repository\nin ~/.mrconfig."; exit 0; }); sub debug { my $str = shift; print "$str" if $verbose; } sub hash { map { $_ => $_ } @_; } sub unique { keys %{{ hash @_ }}; } # how many debian package versions are there? sub get_package_versions { unique map { /^[^:]+: (.*)$/ && $1 } grep { /^Version: / } split "\n", `dpkg-parsechangelog --format rfc822 --all`; } sub get_git_tags { unique split "\n", `git tag -l`; } # git tag checking # there should be a tag 'upstream/$uv' and 'debian/$uv-$dv' for every version $v = "$uv-$dv" sub missing_tags { my ($repo,$git_tags,$type,@versions) = @_; my %git_tags = hash @$git_tags; my @t = grep { !$git_tags{$_} } @versions; say "$repo: ", scalar @t, " missing $type tags: ", (join ", ", @t) if @t; } sub check_package_versions_tagged { my ($repo,$git_tags,$package_versions) = @_; debug "\tcheck_package_versions_tagged\n"; unless (grep /-/, @$package_versions) { # native package missing_tags($repo, $git_tags, "package version", @$package_versions); } else { my $prefix = <.git/refs/tags/upstream*> ? "upstream/" : ""; missing_tags($repo, $git_tags, "upstream", unique map { /^(.*)-[^-]+$/ && "$prefix$1" } grep /-/, @$package_versions); missing_tags($repo, $git_tags, "debian", map { "debian/$_" } @$package_versions); } } # git branch checking sub get_git_branches { my $prefix = shift; unique map { m{$prefix/(.*)$} ? $1 : $_ } split "\n", `git for-each-ref '--format=%(refname)' $prefix`; } sub get_git_local_branches { get_git_branches "refs/heads"; } sub get_git_origin_branches { get_git_branches "refs/remotes/origin"; } # are all local branches represented at origin? sub check_local_branches_at_origin { my ($repo,$origin_branches,$local_branches) = @_; debug "\tcheck_local_branches_at_origin\n"; debug "\t\tlocal branches: " . (join ",", @$local_branches) . "\n"; debug "\t\torigin branches: " . (join ",", @$origin_branches) . "\n"; my %origin_branches = hash @$origin_branches; my @missing = grep { !$origin_branches{$_} } @$local_branches; say "$repo: local branches not present in origin: ",join ",", @missing if @missing; } # are all branches up-to-date? sub check_branches_up_to_date { my ($repo,$origin_branches,$local_branches) = @_; debug "\tcheck_branches_up_to_date\n"; my %local_branches = hash @$local_branches; foreach my $b (grep { $local_branches{$_} } @$origin_branches) { debug "\t\t$b\n"; my $l = `git for-each-ref --format='%(objectname)' refs/remotes/origin/$b`; my $r = `git for-each-ref --format='%(objectname)' refs/heads/$b`; print "$repo: local branch $b does not match origin branch $b\n" unless $r eq $l; } } sub is_gitrepo { -d ".git"; } sub repo_is_debian_package { -f "debian/changelog"; # XXX check for 'debian' branch too } my @repos; if (@ARGV) { @repos = @ARGV; } else { my $cfg = new Config::IniFiles( -file => catfile($ENV{'HOME'}, ".mrconfig")); @repos = $cfg->Sections; } foreach my $repo (@repos) { my $dest = catfile($ENV{'HOME'}, $repo); unless(chdir $dest) { warn "can't chdir to $dest :$!\n"; next; } debug "$dest\n"; if (is_gitrepo) { my @origin_branches = get_git_origin_branches; my @local_branches = get_git_local_branches; check_local_branches_at_origin($repo, \@origin_branches, \@local_branches); check_branches_up_to_date($repo, \@origin_branches, \@local_branches); if (repo_is_debian_package) { my @package_versions = get_package_versions; my @git_tags = get_git_tags; check_package_versions_tagged($repo, \@git_tags, \@package_versions); } } }