#!/usr/bin/perl

use strict;
use warnings;
use 5.010;
use Getopt::Long;
use Pod::Usage qw( pod2usage );
use GitLab::API::v4;
use GitLab::API::v4::Constants qw( :all );
use Git;
use JSON;
use utf8::all;
use File::Spec;

# config
## defaults
my %config = (
    api_url                         => 'https://salsa.debian.org/api/v4',
    private_token                   => '',
    perl_team_path                  => 'perl-team',
    perl_team_id                    => 2663,
    perl_team_interpreter_path      => 'perl-team/interpreter',
    perl_team_interpreter_id        => 2664,
    perl_team_modules_path          => 'perl-team/modules',
    perl_team_modules_id            => 2665,
    perl_team_modules_packages_path => 'perl-team/modules/packages',
    perl_team_modules_packages_id   => 2666,
    perl_team_modules_attic_path    => 'perl-team/modules/attic',
    perl_team_modules_attic_id      => 2667,
    perl_team_modules_meta_path     => 'perl-team/modules/meta',
    perl_team_modules_meta_id       => 13881,
    perl_team_modules_website_path  => 'perl-team/modules/website',
    perl_team_modules_website_id    => 13873,
    perl_team_pages_path            => 'perl-team/perl-team.pages.debian.net',
    perl_team_pages_id              => 11266,
    perl_team_scripts_path          => 'perl-team/scripts',
    perl_team_scripts_id            => 13429,
    packages                        => '',
);

## update from environment / config file
## this is also exported by dpt(1) from ~/.dpt.conf / ~/.config/dpt.conf
## use DPT_SALSA_FOO as $config{foo}
foreach ( keys %config ) {
    my $KEY = 'DPT_SALSA_' . uc($_);
    $config{$_} = $ENV{$KEY} if $ENV{$KEY};
}
## also read DPT_PACKAGES
$config{packages} = $ENV{DPT_PACKAGES};

# commandline
## options
my %opts;
GetOptions( \%opts, 'help|?', 'man', 'json', 'all', 'attic', 'on', 'off' )
    or pod2usage(2);
pod2usage(1) if $opts{help};
pod2usage( -exitval => 0, -verbose => 2 ) if $opts{man};
pod2usage(    # don't check earlier to allow for --help/--man
    -msg      => "E: DPT_SALSA_PRIVATE_TOKEN not set.\n",
    -exitval  => 2,
    -verbose  => 99,
    -sections => "SYNOPSIS|CONFIGURATION",
) unless $config{private_token};

## subcommand and arguments
my $command = shift @ARGV;
pod2usage("E: No subcommand given.\n") unless $command;
my @args = @ARGV;

# our API object
my $api = GitLab::API::v4->new(
    url           => $config{api_url},
    private_token => $config{private_token},
);

# data
my %levels_name = (
    guest     => $GITLAB_ACCESS_LEVEL_GUEST,
    reporter  => $GITLAB_ACCESS_LEVEL_REPORTER,
    developer => $GITLAB_ACCESS_LEVEL_DEVELOPER,
    master    => $GITLAB_ACCESS_LEVEL_MASTER,
    owner     => $GITLAB_ACCESS_LEVEL_OWNER,
);

my %levels_code = (
    $GITLAB_ACCESS_LEVEL_GUEST     => 'guest',
    $GITLAB_ACCESS_LEVEL_REPORTER  => 'reporter',
    $GITLAB_ACCESS_LEVEL_DEVELOPER => 'developer',
    $GITLAB_ACCESS_LEVEL_MASTER    => 'master',
    $GITLAB_ACCESS_LEVEL_OWNER     => 'owner',
);

# run command
if ( $command eq 'version' ) {
    version();
} elsif ( $command eq 'current_user' ) {
    current_user();
} elsif ( $command eq 'adduser' ) {
    adduser();
} elsif ( $command eq 'removeuser'
    or $command eq 'rmuser'
    or $command eq 'deluser' )
{
    removeuser();
} elsif ( $command eq 'changeuser' ) {
    changeuser();
} elsif ( $command eq 'listmembers'
    or $command eq 'listmember'
    or $command eq 'lsmembers'
    or $command eq 'lsmember' )
{
    listmembers();
} elsif ( $command eq 'listrepos'
    or $command eq 'lsrepos'
    or $command eq 'listrepo'
    or $command eq 'lsrepo' )
{
    listrepos();
} elsif ( $command eq 'createrepo' ) {
    createrepo();
} elsif ( $command eq 'pushrepo' ) {
    pushrepo();
} elsif ( $command eq 'configurerepo' ) {
    configurerepo();
} elsif ( $command eq 'changerepo' ) {
    changerepo();
} elsif ( $command eq 'kgb' ) {
    kgb();
} elsif ( $command eq 'mrconfig' ) {
    mrconfig();
} elsif ( $command eq 'githashes' ) {
    githashes();
} elsif ( $command eq 'help' ) {
    pod2usage(1);
} else {
    pod2usage("E: Unknown subcommand: $command.\n");
}
exit;

# subcommand implementations
## version()
sub version {
    my $version = $api->version();
    if ( $opts{json} ) {
        say prettyjson($version);
    } else {
        say "Version:  " . $version->{version};
        say "Revision: " . $version->{revision};
    }
}

## current_user()
sub current_user {
    my $current_user = $api->current_user();
    if ( $opts{json} ) {
        say prettyjson($current_user);
    } else {
        say "Username: " . $current_user->{username};
        say "Name:     " . $current_user->{name};
        say "Email:    " . $current_user->{email};
    }
}

## adduser()
sub adduser {
    my ( $user, $level ) = @args;
    die 'Required parameter username|userid missing.' unless $user;
    $level ||= 'master';

    my $user_id = user2userid($user);

    my $access_level = $levels_name{ lc($level) };
    die "Unknown access level '$level'." unless $access_level;

    $api->add_group_member( $config{perl_team_modules_id},
        { user_id => $user_id, access_level => $access_level } );
}

## changeuser()
sub changeuser {
    my ( $level, $user ) = @args;
    die 'Required parameter "access level" missing.' unless $level;
    die 'Required parameter username|userid missing.'
        unless ( $user or $opts{all} );
    my @userids = $opts{all} ? listmembers() : $user;

    foreach (@userids) {
        my $user_id = user2userid($_);

        my $access_level = $levels_name{ lc($level) };
        die "Unknown access level '$level'." unless $access_level;

        $api->update_group_member(
            $config{perl_team_modules_id},
            $user_id, { access_level => $access_level },
        );
    }
}

## removeuser()
sub removeuser {
    my ($user) = @args;
    die 'Required parameter username|userid missing.' unless $user;
    my $user_id = user2userid($user);
    $api->remove_group_member( $config{perl_team_modules_id}, $user_id );
}

## listmembers()
sub listmembers {
    my $paginator
        = $api->paginator( 'group_members', $config{perl_team_modules_id} );
    my @userids;
    while ( my $user = $paginator->next ) {
        push @userids, $user->{id};
        next if $opts{all};
        if ( $opts{json} ) {
            say prettyjson($user);
        } else {
            my $access_level = $levels_code{ $user->{access_level} };
            say "Id:           " . $user->{id};
            say "Username:     " . $user->{username};
            say "Name:         " . $user->{name};
            say "Access level: " . $access_level;
        }
    }
    return @userids;
}

## listrepos()
sub listrepos {
    my $paginator = $api->paginator(
        'group_projects',
        $opts{attic}
        ? $config{perl_team_modules_attic_id}
        : $config{perl_team_modules_packages_id},
        { order_by => 'name', sort => 'asc' }
    );
    my @repoids;
    while ( my $repo = $paginator->next ) {
        push @repoids, $repo->{id};
        next if $opts{all};
        if ( $opts{json} ) {
            say prettyjson($repo);
        } else {
            say "Id:   " . $repo->{id};
            say "Name: " . $repo->{name};
            say "URL:  " . $repo->{web_url};
        }
    }
    return @repoids;
}

## createrepo()
sub createrepo {
    my ($reponame) = shift || @args;
    die 'Required parameter repositoryname missing.' unless $reponame;

    my $repo = $api->create_project(
        {   name         => $reponame,
            namespace_id => $config{perl_team_modules_packages_id},
            description  => "Debian package $reponame",
            visibility   => 'public',
            wiki_enabled => 0,
        }
    );
    configurerepo( $repo->{id} );
}

## pushrepo()
sub pushrepo {
    my $pkg = qx/dh_testdir && dpkg-parsechangelog --show-field Source/;
    die "Can't find the name of this source package." unless $pkg;
    chomp $pkg;

    my $localrepo = Git->repository();
    my %remotes = map { my ( $name, $url ) = split ' '; $name => $url; }
        $localrepo->command( 'remote', '--verbose', 'show' );
    my $salsaurl
        = 'git@salsa.debian.org:'
        . $config{perl_team_modules_packages_path}
        . "/$pkg.git";
    if ( !$remotes{'origin'} ) {
        $localrepo->command( 'remote', 'add', 'origin', $salsaurl );
    } elsif ( $remotes{'origin'}
        !~ m!^git\@salsa.debian.org:$config{perl_team_modules_packages_path}/$pkg.git$!
        )
    {
        $localrepo->command( 'remote', 'set-url', 'origin', $salsaurl );
    }

    createrepo($pkg)
        unless $api->project(
        $config{perl_team_modules_packages_path} . '/' . $pkg );

    # or --mirror instead of the two? also pushes origin/ branches
    $localrepo->command( 'push', '--all', '--verbose', '--set-upstream',
        'origin' );
    $localrepo->command( 'push', '--tags', '--verbose', 'origin' );
}

## configurerepo()
sub configurerepo {
    my ($repo) = shift || @args;
    die 'Required parameter reponame|repoid missing.'
        unless ( $repo or $opts{all} );
    my @repoids = $opts{all} ? listrepos() : $repo;

    foreach (@repoids) {
        my $repo_path = repo2repopath($_);
        my $repo_name = $api->project($repo_path)->{name};

        # webhooks: cleanup and set: tagpending and kgb
        my $hooks = $api->project_hooks($repo_path);
        $api->delete_project_hook( $repo_path, $_->{id} ) foreach @{$hooks};

        $api->create_project_hook(
            $repo_path,
            {   url =>
                    "https://webhook.salsa.debian.org/tagpending/$repo_name",
                push_events => 1,
            }
        );
        $api->create_project_hook(
            $repo_path,
            {   url =>
                    'http://kgb.debian.net:9418/webhook/?channel=debian-perl',
                push_events           => 1,
                issues_events         => 1,
                merge_requests_events => 1,
                tag_push_events       => 1,
                note_events           => 1,
                job_events            => 1,
                pipeline_events       => 1,
                wiki_events           => 1,
            }
        );
    }
}

## kgb()
sub kgb {
    my ($repo) = shift || @args;
    die 'Required parameter reponame|repoid missing.'
        unless ( $repo or $opts{all} );
    my @repoids = $opts{all} ? listrepos() : $repo;

    ( $opts{on} xor $opts{off} )
        or die "Exactly on of --on or --off is required";

    foreach (@repoids) {
        my $repo_path = repo2repopath($_);
        my $repo_name = $api->project($repo_path)->{name};

        my $hooks = $api->project_hooks($repo_path);

        if ($opts{off}) {
            foreach ( @{$hooks} ) {
                $api->delete_project_hook( $repo_path, $_->{id} )
                    if $_->{url} =~ m{^https?://kgb\.debian\.net(?::\d+)?/};
            }
        }

        if ( $opts{on} ) {
            my $already_present;

            foreach ( @{$hooks} ) {
                $already_present = 1, last
                    if $_->{url} =~ m{^https?://kgb\.debian\.net(?::\d+)?/};
            }

            unless ($already_present) {
                $api->create_project_hook(
                    $repo_path,
                    {   url =>
                            'http://kgb.debian.net:9418/webhook/?channel=debian-perl',
                        push_events           => 1,
                        issues_events         => 1,
                        merge_requests_events => 1,
                        tag_push_events       => 1,
                        note_events           => 1,
                        job_events            => 1,
                        pipeline_events       => 1,
                        wiki_events           => 1,
                    }
                );
            }
        }
    }
}

## changerepo()
sub changerepo {
    my ( $repo, $property, $value ) = @args;
    die 'Required parameter reponame|repoid missing.'  unless $repo;
    die 'Required parameter name|description missing.' unless $property;
    die "Unknown property '$property'. Valid cases: 'name' or 'description'."
        unless ( $property eq 'name' or $property eq 'description' );
    die 'Required parameter "new value" missing.' unless $value;

    my $repo_path = repo2repopath($repo);
    $api->edit_project( $repo_path, { $property => $value } );
    $api->edit_project( $repo_path, { path      => $value } )
        if $property eq 'name';
}

## mrconfig
sub mrconfig {
    die 'DPT_PACKAGES not set.' unless $config{packages};
    my $outdir = File::Spec->catdir( $config{packages}, '..' );
    die "Can't find directory '$outdir'." unless -d $outdir;

    my $mrconfig = File::Spec->catfile( $outdir, '.mrconfig' );
    die "Can't find '.mrconfig' in '$outdir'" unless -f $mrconfig;
    my $outfile = File::Spec->catfile( $outdir, '.mrconfig.packages' );
    open( my $fh, '>', "$outfile.new" )
        or die "Can't open '$outfile.new': $!";

    my $paginator = $api->paginator(
        'group_projects',
        $config{perl_team_modules_packages_id},
        { order_by => 'name', sort => 'asc' }
    );
    while ( my $repo = $paginator->next() ) {
        my $reponame = $repo->{name};

        # stdout
        say "[packages/$reponame]";
        say "checkout = git_checkout $reponame\n";

        # file
        say "[packages/$reponame]";
        say "checkout = git_checkout $reponame\n";
    }
    rename "$outfile.new", "$outfile"
        or die "Can't rename '$outfile.new' to '$outfile': $!";
    close $fh;
}

## githashes
sub githashes {
    die 'DPT_PACKAGES not set.' unless $config{packages};
    my $outdir = File::Spec->catdir( $config{packages}, '..', '.git.hashes' );
    die "Can't find directory '$outdir'." unless -d $outdir;

    my @currentfiles = glob("$outdir/*");
    s{$outdir/}{}o for @currentfiles;
    my %currentfiles = map( ( $_ => 1 ), @currentfiles );

    my $paginator = $api->paginator(
        'group_projects',
        $config{perl_team_modules_packages_id},
        { order_by => 'name', sort => 'asc' }
    );
    while ( my $repo = $paginator->next() ) {
        my ( $repotags, $repobranches, $repohashes );

        # eval api calls so we don't die in the loop
        my ( $tags, $branches );
        # disable tags retrieval to save time
        # eval { $tags = $api->tags( $repo->{id} ); };
        # $repotags->{ 'refs/tags/' . $_->{name} } = $_->{commit}->{id}
        #     foreach @{$tags};
        # $repohashes->{tags} = $repotags if $repotags;
        eval { $branches = $api->branches( $repo->{id} ); };
        $repobranches->{ 'refs/heads/' . $_->{name} } = $_->{commit}->{id}
            foreach @{$branches};
        $repohashes->{branches} = $repobranches if $repobranches;
        # $repohashes->{trunk} seems to be a PET artifact and not used by compare-hashes

        my $outfile = File::Spec->catfile( $outdir, $repo->{name} );
        open( my $fh, '>', "$outfile.new" )
            or die "Can't open '$outfile.new': $!";
        print $fh prettyjson($repohashes);
        close $fh;
        rename "$outfile.new", "$outfile"
            or die "Can't rename '$outfile.new' to '$outfile': $!";
        delete $currentfiles{ $repo->{name} };
    }

    unlink "$outdir/$_" for keys %currentfiles;
}

# helper functions
## prettyjson($data)
sub prettyjson {
    my $data = shift;
    my $json = JSON->new->utf8->pretty->canonical->allow_nonref();
    $json->encode($data);
}

## username2userid($username)
sub username2userid {
    my $username = shift;
    my $users = $api->users( { username => $username } );
    die "More than one user with username '$username'."
        if scalar @{$users} > 1;
    die "Username '$username' not found." if scalar @{$users} < 1;
    return $users->[0]->{id};
}

## user2userid($string)
sub user2userid {
    my $user = shift;
    return $user if $user =~ /^\d+$/;
    return username2userid($user) if $user =~ /^[\w-]+$/;
    die "Parameter '$user' doesn't look like a userid or a username.";
}

## reponame2repoid($reponame)
## XXX unused
sub reponame2repoid {
    my $reponame = shift;
    my $repos = $api->projects( { search => $reponame } );
    die "More than one repository with name '$reponame'."
        if scalar @{$repos} > 1;
    die "Repository name '$reponame' not found." if scalar @{$repos} < 1;
    return $repos->[0]->{id};
}

## repo2repoid($string)
## XXX unused
sub repo2repoid {
    my $repo = shift;
    return $repo if $repo =~ /^\d+$/;
    return reponame2repoid($repo) if $repo =~ /^\w+$/;
    die
        "Parameter '$repo' doesn't look like a repositoryid or a repositoryname.";
}

## reponame2repopath($reponame)
sub reponame2repopath {
    my $reponame = shift;
    my $repopath_packages
        = $config{perl_team_modules_packages_path} . '/' . $reponame;
    my $repopath_attic
        = $config{perl_team_modules_attic_path} . '/' . $reponame;
    my $repo
        = $api->project($repopath_packages) || $api->project($repopath_attic);
    die
        "Repository name '$reponame' not found in '$config{perl_team_modules_packages_path}' or '$config{perl_team_modules_attic_path}'."
        unless $repo;
    return $repo->{path_with_namespace};
}

## repo2repopath($string)
sub repo2repopath {
    my $repo = shift;
    return $repo if $repo =~ /^\d+$/;
    return reponame2repopath($repo) if $repo =~ /^[\w-]+$/;
    die
        "Parameter '$repo' doesn't look like a repositoryid or a repositoryname.";
}

__END__

=head1 NAME

B<dpt-salsa> - manage repositories and members of the I<perl-team> on I<salsa.debian.org>

=head1 SYNOPSIS

B<dpt salsa> [--help|--man|--json|--all|--attic] I<subcommand> [parameters]

=head1 DESCRIPTION

B<dpt-salsa> is basically a wrapper around L<GitLab::API::v4>, similar to
L<gitlab-api-v4(1)>, with various variables regarding I<salsa.debian.org>
and the I<modules> subgroup of the I<perl-team> group already preset and
typical method calls encapsulated.

It offers subcommands to manage repositories and members of the I<modules>
subgroup with hopefully less typing then calling the API manually each time.

Make sure to check the L</CONFIGURATION> section below if you use
B<dpt-salsa> for the first time.

=head1 SUBCOMMANDS

=head2 for managing repositories

=head3 I<createrepo> I<repositoryname>

Creates a new repository in the I<modules> subgroup and calls C<configurerepo()>.

Parameters:

=over

=item repositoryname

Name of the repository to be added; usually the package name.
Required.

=back

=head3 I<pushrepo>

Creates a new repository in the I<modules> subgroup and pushes the local
repository.

=head3 I<configurerepo> I<repositoryid|repositoryname>

Sets up the default webhooks and services for one repository.

Parameters:

=over

=item repositoryid|repositoryname

The repository to be configured. Either its id (\d+) or name (\w+).
Required.

=back

=head3 I<configurerepo> I<--all> [--attic]

Sets up the default webhooks and services for all active (or, with
C<--attic>, archived) repositories.

=head3 I<kgb> I<repo>|I<--all> [--attic] I<--on|--off>

Install (C<--on>) or remove (C<--off>) the KGB IRC notification webhook
for the given, all active (C<--all>), or all
archive (C<--attic>) repositories.

If a KGB notification webhook is already present, C<--on> does nothing.

=head3 I<changerepo> I<repositoryid|repositoryname> I<name|description> C<"parameter">

Changes the name (and the path) or the description of a repository.

Parameters:

=over

=item repositoryid|repositoryname

The repository to be configured. Either its id (\d+) or name (\w+).
Required.

=item name|description

What should be changed? The name or the description of the repository.
Required.

=item parameter

The new name or description.
Required.

=back

=head3 I<toattic|fromattic> I<repositoryid|repositoryname>

Moves a repository to/from the B<attic> sub-group of the B<modules> sub-group.
Useful when a package is removed from the archive.

B<WARNING>: Not implemented. According to
L<https://docs.gitlab.com/ce/api/groups.html#transfer-project-to-group> this
only works for admins. Trying it via B<GitLab::API::v4> leads to a C<403
forbidden> error. Use the web interface (which requires an extra
confirmation step) for now.

=head3 I<listrepos> [--json] [--attic]

Show all active (or, with C<--attic>, archived) repositories in the
I<modules> subgroup.

If used with C<--all>, returns repository ids and does not output anything;
for internal use.

=head2 for managing users

=head3 I<adduser> I<username|userid> [access_level]

Adds a user to the I<modules> subgroup of the I<perl-team> group.

Parameters:

=over

=item username|userid

The user to be added. Either their id (\d+) or their username (\w+).
Required.

=item access_level

One of I<GitLab>'s access levels: guest, reporter, developer, master, owner.
Optional, defaults to C<master>.

=back

=head3 I<removeuser> I<username|userid>

Removes a user from the I<modules> subgroup of the I<perl-team> group.

Parameters:

=over

=item username|userid

The user to be removed. Either their id (\d+) or their username (\w+).
Required.

=back

=head3 I<changeuser> I<access_level> I<username|userid>

Change the access level of one user in the I<modules> subgroup of the
I<perl-team> group.

Parameters:

=over

=item access_level

One of I<GitLab>'s access levels: guest, reporter, developer, master, owner.
Required.

=item username|userid

The user whose access level is to be changed. Either their id (\d+) or their
username (\w+).
Required.

=back

=head3 I<changeuser> I<access_level> I<--all>

Change the access level of all users in the I<modules> subgroup of the
I<perl-team> group.

Parameters:

=over

=item access_level

One of I<GitLab>'s access levels: guest, reporter, developer, master, owner.
Required.

=back

=head3 I<listmembers> [--json]

Show all members of the I<modules> subgroup of the I<perl-team> group.

If used with C<--all>, returns user ids and does not output anything; for
internal use.

=head2 others

=head3 I<mrconfig>

Helper for creating a F<.mrconfig.packages> file in the local clone of
C<meta.git> for all active packages of the I<modules> subgroup of the
I<perl-team> group. Also writes to stdout which can be included from
F<.mrconfig>.

=head3 I<githashes>

Helper for creating F<.git.hashes/PKGNAME> files in the local clone of
C<meta.git> for all active packages of the I<modules> subgroup of the
I<perl-team> group which are then used by B<compare-hashes> in F<.mrconfig>.

=head3 I<current_user> [--json]

Outputs information about the user whose I<GitLab> token is used.

=head3 I<help>

Same as option B<--help>.

=head3 I<version> [--json]

Returns the version of the I<GitLab> instance running on I<salsa.debian.org>.

This subcommand is pretty useless, the only excuse for its existence is the
ability to test if everything is working fine.

=head1 OPTIONS

=over

=item --help

Show short help.

=item --man

Show complete manpage.

=item --all

Act on all users or repositories, not a single named one.
Only for specific subcommands, as noted in their description.

=item --attic

Act on archived repositories instead of active ones.
Only for specific subcommands, as noted in their description.

=item --json

Format output as JSON instead of human-targeted text.
Only for specific subcommands, as noted in their description.

=back

=head1 CONFIGURATION

B<dpt-salsa> uses the following environment variables, set either directly
or via F<~/.dpt.conf> / F<~/.config/dpt.conf>:

=over

=item DPT_SALSA_PRIVATE_TOKEN

required, no default, obviously

These tokens are created at
L<https://salsa.debian.org/profile/personal_access_tokens>.

=item DPT_SALSA_API_URL

optional, default: https://salsa.debian.org/api/v4

=item DPT_SALSA_PERL_TEAM_PATH

optional, default: perl-team

=item DPT_SALSA_PERL_TEAM_ID

optional, default: 2663

=item DPT_SALSA_PERL_TEAM_INTERPRETER_PATH

optional, default: perl-team/interpreter

=item DPT_SALSA_PERL_TEAM_INTERPRETER_ID

optional, default: 2664

=item DPT_SALSA_PERL_TEAM_MODULES_PATH

optional, default: perl-team/modules

=item DPT_SALSA_PERL_TEAM_MODULES_ID

optional, default: 2665

=item DPT_SALSA_PERL_TEAM_MODULES_PACKAGES_PATH

optional, default: perl-team/modules/packages

=item DPT_SALSA_PERL_TEAM_MODULES_PACKAGES_ID

optional, default: 2666

=item DPT_SALSA_PERL_TEAM_MODULES_ATTIC_PATH

optional, default: perl-team/modules/attic

=item DPT_SALSA_PERL_TEAM_MODULES_ATTIC_ID

optional, default: 2667

=item DPT_SALSA_PERL_TEAM_MODULES_META_PATH

optional, default: perl-team/modules/meta

=item DPT_SALSA_PERL_TEAM_MODULES_META_ID

optional, default: 13881

=item DPT_SALSA_PERL_TEAM_MODULES_WEBSITE_PATH

optional, default: perl-team/modules/website

=item DPT_SALSA_PERL_TEAM_MODULES_WEBSITE_ID

optional, default: 13873

=item DPT_SALSA_PERL_TEAM_PAGES_PATH

optional, default: perl-team/perl-team.pages.debian.net

=item DPT_SALSA_PERL_TEAM_PAGES_ID

optional, default: 11266

=item DPT_SALSA_PERL_TEAM_SCRIPTS_PATH

optional, default: perl-team/scripts

=item DPT_SALSA_PERL_TEAM_SCRIPTS_ID

optional, default: 13429

=item DPT_PACKAGES

only used by the B<mrconfig> subcommand, no default;
most probably already set for use with other B<dpt> commands.

=back

Cf. L<dpt-config(5)>.

=head1 SEE ALSO

L<https://salsa.debian.org/perl-team>

L<GitLab::API::v4>

L<https://docs.gitlab.com/ce/api/>

=head1 COPYRIGHT AND LICENSE

Copyright 2018, gregor herrmann E<lt>gregoa@debian.orgE<gt>

Released under the same terms as Perl itself, i.e. Artistic or GPL-1+.
