App-Multigit

 view release on metacpan or  search on metacpan

lib/App/Multigit.pm  view on Meta::CPAN

package App::Multigit;

use 5.014;
use strict;
use warnings FATAL => 'all';

use List::UtilsBy qw(sort_by);
use Capture::Tiny qw(capture);
use File::Find::Rule;
use Future::Utils qw(fmap);
use Path::Class;
use Config::INI::Reader;
use Config::INI::Writer;
use IPC::Run;
use Try::Tiny;

use App::Multigit::Future;
use App::Multigit::Repo;
use App::Multigit::Loop qw(loop);

use Exporter 'import';

our @EXPORT_OK = qw/
    mgconfig mg_parent
    all_repositories selected_repositories
    base_branch set_base_branch mg_each
    write_config
/;

=head1 NAME

App::Multigit - Run commands on a bunch of git repositories without having to
deal with git subrepositories.

=cut

our $VERSION = '0.18';

=head1 PACKAGE VARS

=head2 %BEHAVIOUR

This holds configuration set by options passed to the C<mg> script itself.

Observe that C<mg [options] command [command-options]> will pass C<options> to
C<mg>, and C<command-options> to C<mg-command>. It is those C<options> that will
affect C<%BEHAVIOUR>.

Scripts may also therefore change C<%BEHAVIOUR> themselves, but it is probably
badly behaved to do so.

=head3 report_on_no_output

Defaults to true; this should be used by scripts to determine whether to bother
mentioning repositories that gave no output at all for the given task. If you
use C<App::Multigit::Repo::report>, this will be honoured by default.

Controlled by the C<MG_REPORT_ON_NO_OUTPUT> environment variable.

=head3 ignore_stdout

=head3 ignore_stderr

These default to false, and will black-hole these streams wherever we have
control to do so.

Controlled by the C<MG_IGNORE_{STDOUT,STDERR}> environment variables.

=head3 concurrent_processes

Number of processes to run in parallel. Defaults to 20.

Controlled by the C<MG_CONCURRENT_PROCESSES> environment variable.

=head3 skip_readonly

Do nothing to repositories that have C<readonly = 1> set in C<.mgconfig>.

Controlled by the C<MG_SKIP_READONLY> environment variable.

=cut

our %BEHAVIOUR = (
    report_on_no_output => $ENV{MG_REPORT_ON_NO_OUTPUT} // 1,
    ignore_stdout       => !!$ENV{MG_IGNORE_STDOUT},
    ignore_stderr       => !!$ENV{MG_IGNORE_STDERR},
    concurrent          => $ENV{MG_CONCURRENT_PROCESSES} // 20,
    skip_readonly       => !!$ENV{MG_SKIP_READONLY},
    output_only         => !!$ENV{MG_OUTPUT_ONLY},
);

=head2 @SELECTED_REPOS

If this is not empty, it should contain paths to repositories. Relative paths
will be determined relative to L<C<<mg_root>>|/mg_root>.

Instead of using the C<.mgconfig>, the directories in here will be used as the
list of repositories on which to work.

Each repository's C<origin> remote will be interrogated. If this exists in the
C<.mgconfig> then it will be used as normal; otherwise, it will be treated as
though it had the default configuration.

=cut

our @SELECTED_REPOS;

=head1 FUNCTIONS

These are not currently exported.

=head2 mgconfig

Returns C<.mgconfig>. This is a stub to be later configurable, but also
to stop me typoing it all the time.

=cut

sub mgconfig() {
    return '.mgconfig';
}

=head2 mg_parent

Tries to find the closest directory with an C<mgconfig> in it. Dies if there is
no mgconfig here. Optionally accepts the directory to start with.

=cut

sub mg_parent {
    my $pwd;
    if (@_) {
        $pwd = dir(shift);
    }
    else {
        $pwd = dir;
    }
    $pwd = $pwd->absolute;

    PARENT: {
        do {
            return $pwd if -e $pwd->file(mgconfig);
            last PARENT if $pwd eq $pwd->parent;
        }
        while ($pwd = $pwd->parent);
    }

    die "Could not find .mgconfig in any parent directory";
}



( run in 1.326 second using v1.01-cache-2.11-cpan-13bb782fe5a )