package Local::MrRepo; # Copyright (C) 2019 Sean Whitton # # 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 3 of the License, or (at # your option) any later version. # # 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. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . use strict; use warnings; use lib "$ENV{HOME}/src/dotfiles/perl5"; use Cwd; use File::Spec::Functions qw(catfile rel2abs); use Exporter 'import'; use File::Find; # Quoting perldoc perlmodlib: "As a general rule, if the module is # trying to be object oriented then export nothing. If it's just a # collection of functions then @EXPORT_OK anything but use @EXPORT # with caution." our @EXPORT_OK = qw( new_repo ); # Local::MrRepo::Repo::Git and its subclass require non-core # modules, so we might not be able to load them. If so we'll fall # back on Local::MrRepo::Repo # # ~/bin/locmaint and Local::Homedir use the simpler technique of just # # return unless eval "use Foo::Bar; 1"; # # at the top of functions that require use Foo::Bar. However, for # MrRepo classes we want to rethrow exceptions (other than import # failures) to make it easier to debug those classes. # # The purpose of wrapping conditional module inclusion in BEGIN is to # provide subroutine definitions etc. to the compiler as early as # possible. AFAICT that's not really needed when importing modules # providing only an OO interface, however, because they don't usually # export anything at all. That's why the simpler technique suffices # over in ~/bin/locmaint and Local::Homedir, for Data::Manip and # Git::Wrapper. # # References: Perl Cookbook 12.2, "Trapping Errors in require or use" # Modern Perl, ch. 8, "Catching Exceptions" my $have_mrrepo_git; # must not initialise here BEGIN { local $@; eval "require Local::MrRepo::Repo::Git"; if (my $exception = $@) { die $exception unless $exception =~ /^Can't locate .+ in \@INC/; $have_mrrepo_git = 0; } else { Local::MrRepo::Git->import(); local $@; eval "require Local::MrRepo::Repo::Git::Annex"; if (my $exception = $@) { die $exception unless $exception =~ /^Can't locate .+ in \@INC/; $have_mrrepo_git = 0; } else { Local::MrRepo::Git::Annex->import(); $have_mrrepo_git = 1; } } } sub new_repo { my $dir = shift; # This matches git_test in mr(1). That means we don't count bare # repos, and we're relying on that atm, because ~/lib/nmbug-* # should be checked using their custom `mr checkout` commands. Maybe # we should instead be seeing whether `mr config $dir checkout` # matches /^git clone/, but that means disagreeing with mr(1) # about which repos are git repos. if ($have_mrrepo_git && -d catfile($dir, ".git")) { if (has_annex_objects($dir)) { return Local::MrRepo::Repo::Git::Annex->new($dir); } else { return Local::MrRepo::Repo::Git->new($dir); } } else { return Local::MrRepo::Repo->new($dir); } } sub has_annex_objects { my $repo = rel2abs(shift); my $objects_dir = catfile($repo, ".git", "annex", "objects"); return 0 unless -d $objects_dir; # File::Find::find changes the working directory; undo that # (no_chdir doesn't seem to be sufficient) my $cwd = getcwd(); my $found = 0; find( sub { if (-f $File::Find::name) { $found = 1; # File::Find does not really support exiting the search goto MRREPO_DONE_ANNEX_OBJECTS; } }, $objects_dir ); MRREPO_DONE_ANNEX_OBJECTS: chdir $cwd; return $found; } 1;