1
0
Fork 0

Adding upstream version 2.25.15.

Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
This commit is contained in:
Daniel Baumann 2025-06-21 11:04:07 +02:00
parent 10737b110a
commit b543f2e88d
Signed by: daniel.baumann
GPG key ID: BCC918A2ABD66424
485 changed files with 191459 additions and 0 deletions

427
lib/Devscripts/Salsa.pm Executable file
View file

@ -0,0 +1,427 @@
package Devscripts::Salsa;
=head1 NAME
Devscripts::Salsa - salsa(1) base object
=head1 SYNOPSIS
use Devscripts::Salsa;
exit Devscripts::Salsa->new->run
=head1 DESCRIPTION
Devscripts::Salsa provides salsa(1) command launcher and some common utilities
methods.
=cut
use strict;
use Devscripts::Output;
use Devscripts::Salsa::Config;
BEGIN {
eval "use GitLab::API::v4;use GitLab::API::v4::Constants qw(:all)";
if ($@) {
print STDERR "You must install GitLab::API::v4\n";
exit 1;
}
}
use Moo;
use File::Basename;
use File::Path qw(make_path);
# Command aliases
use constant cmd_aliases => {
# Alias => Filename -> ./lib/Devscripts/Salsa/*.pm
# Preferred terminology
check_projects => 'check_repo',
create_project => 'create_repo',
delete_project => 'del_repo',
delete_user => 'del_user',
list_projects => 'list_repos',
list_users => 'group',
search_groups => 'search_group',
search_projects => 'search_project',
search_users => 'search_user',
update_projects => 'update_repo',
# Catch possible typo (As able to-do multiple items at once)
list_user => 'group',
check_project => 'check_repo',
list_project => 'list_repos',
update_project => 'update_repo',
# Abbreviation
co => 'checkout',
ls => 'list_repos',
mr => 'merge_request',
mrs => 'merge_requests',
schedule => 'pipeline_schedule',
schedules => 'pipeline_schedules',
# Legacy
search => 'search_project',
search_repo => 'search_project',
};
=head1 ACCESSORS
=over
=item B<config> : Devscripts::Salsa::Config object (parsed)
=cut
has config => (
is => 'rw',
default => sub { Devscripts::Salsa::Config->new->parse },
);
=item B<cache> : Devscripts::JSONCache object
=cut
# File cache to avoid polling GitLab too much
# (used to store ids, paths and names)
has _cache => (
is => 'rw',
lazy => 1,
default => sub {
return {} unless ($_[0]->config->cache_file);
my %h;
eval {
my ($cache_file, $cache_dir) = fileparse $_[0]->config->cache_file;
if (!-d $cache_dir) {
make_path $cache_dir;
}
require Devscripts::JSONCache;
tie %h, 'Devscripts::JSONCache', $_[0]->config->cache_file;
ds_debug "Cache opened";
};
if ($@) {
ds_verbose "Unable to create cache object: $@";
return {};
}
return \%h;
},
);
has cache => (
is => 'rw',
lazy => 1,
default => sub {
$_[0]->_cache->{ $_[0]->config->api_url } //= {};
return $_[0]->_cache->{ $_[0]->config->api_url };
},
);
# In memory cache (used to avoid querying the project id twice when using
# update_safe
has projectCache => (
is => 'rw',
default => sub { {} },
);
=item B<api>: GitLab::API::v4 object
=cut
has api => (
is => 'rw',
lazy => 1,
default => sub {
my $r = GitLab::API::v4->new(
url => $_[0]->config->api_url,
(
$_[0]->config->private_token
? (private_token => $_[0]->config->private_token)
: ()
),
);
$r or ds_die "Unable to create GitLab::API::v4 object";
return $r;
},
);
=item User or group in use
=over
=item B<username>
=item B<user_id>
=item B<group_id>
=item B<group_path>
=back
=cut
# Accessors that resolve names, ids or paths
has username => (
is => 'rw',
lazy => 1,
default => sub { $_[0]->id2username });
has user_id => (
is => 'rw',
lazy => 1,
default => sub {
$_[0]->config->user_id || $_[0]->username2id;
},
);
has group_id => (
is => 'rw',
lazy => 1,
default => sub { $_[0]->config->group_id || $_[0]->group2id },
);
has group_path => (
is => 'rw',
lazy => 1,
default => sub {
my ($self) = @_;
return undef unless ($self->group_id);
return $self->cache->{group_path}->{ $self->{group_id} }
if $self->cache->{group_path}->{ $self->{group_id} };
return $self->{group_path} if ($self->{group_path}); # Set if --group
eval {
$self->{group_path}
= $self->api->group_without_projects($self->group_id)
->{full_path};
$self->cache->{group_path}->{ $self->{group_id} }
= $self->{group_path};
};
if ($@) {
ds_verbose $@;
ds_warn "Unexistent group " . $self->group_id;
return undef;
}
return $self->{group_path};
},
);
=back
=head1 METHODS
=over
=item B<run>: main method, load and run command and return Unix result code.
=cut
sub run {
my ($self, $args) = @_;
binmode STDOUT, ':utf8';
# Check group or user id
my $command = $self->config->command;
if (my $tmp = cmd_aliases->{$command}) {
$command = $tmp;
}
eval { with "Devscripts::Salsa::$command" };
if ($@) {
ds_verbose $@;
ds_die "Unknown command $command";
return 1;
}
return $self->$command(@ARGV);
}
=back
=head2 Utilities
=over
=item B<levels_name>, B<levels_code>: convert strings to GitLab level codes
(owner, maintainer, developer, reporter and guest)
=cut
sub levels_name {
my $res = {
# needs GitLab::API::v4::Constants 0.11
# no_access => $GITLAB_ACCESS_LEVEL_NO_ACCESS,
guest => $GITLAB_ACCESS_LEVEL_GUEST,
reporter => $GITLAB_ACCESS_LEVEL_REPORTER,
developer => $GITLAB_ACCESS_LEVEL_DEVELOPER,
maintainer => $GITLAB_ACCESS_LEVEL_MASTER,
owner => $GITLAB_ACCESS_LEVEL_OWNER,
}->{ $_[1] };
ds_die "Unknown access level '$_[1]'" unless ($res);
return $res;
}
sub levels_code {
return {
$GITLAB_ACCESS_LEVEL_GUEST => 'guest',
$GITLAB_ACCESS_LEVEL_REPORTER => 'reporter',
$GITLAB_ACCESS_LEVEL_DEVELOPER => 'developer',
$GITLAB_ACCESS_LEVEL_MASTER => 'maintainer',
$GITLAB_ACCESS_LEVEL_OWNER => 'owner',
}->{ $_[1] };
}
=item B<username2id>, B<id2username>: convert username to an id an reverse
=cut
sub username2id {
my ($self, $user) = @_;
$user ||= $self->config->user || $self->api->current_user->{id};
unless ($user) {
return ds_warn "Token seems invalid";
return 1;
}
unless ($user =~ /^\d+$/) {
return $self->cache->{user_id}->{$user}
if $self->cache->{user_id}->{$user};
my $users = $self->api->users({ username => $user });
return ds_die "Username '$user' not found"
unless ($users and @$users);
ds_verbose "$user id is $users->[0]->{id}";
$self->cache->{user_id}->{$user} = $users->[0]->{id};
return $users->[0]->{id};
}
return $user;
}
sub id2username {
my ($self, $id) = @_;
$id ||= $self->config->user_id || $self->api->current_user->{id};
return $self->cache->{user}->{$id} if $self->cache->{user}->{$id};
my $res = eval { $self->api->user($id)->{username} };
if ($@) {
ds_verbose $@;
return ds_die "$id not found";
}
ds_verbose "$id is $res";
$self->cache->{user}->{$id} = $res;
return $res;
}
=item B<group2id>: convert group name to id
=cut
sub group2id {
my ($self, $name) = @_;
$name ||= $self->config->group;
return unless $name;
if ($self->cache->{group_id}->{$name}) {
$self->group_path($self->cache->{group_id}->{$name}->{path});
return $self->group_id($self->cache->{group_id}->{$name}->{id});
}
my $groups = $self->api->group_without_projects($name);
if ($groups) {
$groups = [$groups];
} else {
$self->api->groups({ search => $name });
}
return ds_die "No group found" unless ($groups and @$groups);
if (scalar @$groups > 1) {
ds_warn "More than one group found:";
foreach (@$groups) {
print <<END;
Id : $_->{id}
Name : $_->{name}
Full name: $_->{full_name}
Full path: $_->{full_path}
END
}
return ds_die "Set the chosen group id using --group-id.";
}
ds_verbose "$name id is $groups->[0]->{id}";
$self->cache->{group_id}->{$name}->{path}
= $self->group_path($groups->[0]->{full_path});
$self->cache->{group_id}->{$name}->{id} = $groups->[0]->{id};
return $self->group_id($groups->[0]->{id});
}
=item B<project2id>: get id of a project.
=cut
sub project2id {
my ($self, $project) = @_;
return $project if ($project =~ /^\d+$/);
my $res;
$project = $self->project2path($project);
if ($self->projectCache->{$project}) {
ds_debug "use cached id for $project";
return $self->projectCache->{$project};
}
unless ($project =~ /^\d+$/) {
eval { $res = $self->api->project($project)->{id}; };
if ($@) {
ds_debug $@;
ds_warn "Project $project not found";
return undef;
}
}
ds_verbose "$project id is $res";
$self->projectCache->{$project} = $res;
return $res;
}
=item B<project2path>: get full path of a project
=cut
sub project2path {
my ($self, $project) = @_;
return $project if ($project =~ m#/#);
my $path = $self->main_path;
return undef unless ($path);
ds_verbose "Project $project => $path/$project";
return "$path/$project";
}
=item B<main_path>: build path using given group or user
=cut
sub main_path {
my ($self) = @_;
my $path;
if ($self->config->path) {
$path = $self->config->path;
} elsif (my $tmp = $self->group_path) {
$path = $tmp;
} elsif ($self->user_id) {
$path = $self->username;
} else {
ds_warn "Unable to determine project path";
return undef;
}
return $path;
}
# GitLab::API::v4 does not permit to call /groups/:id with parameters.
# It takes too much time for the "debian" group, since it returns the list of
# all projects together with all the details of the projects
sub GitLab::API::v4::group_without_projects {
my $self = shift;
return $self->_call_rest_client('GET', 'groups/:group_id', [@_],
{ query => { with_custom_attributes => 0, with_projects => 0 } });
}
1;
=back
=head1 AUTHOR
Xavier Guimard E<lt>yadd@debian.orgE<gt>
=head1 COPYRIGHT AND LICENSE
Copyright 2018, Xavier Guimard E<lt>yadd@debian.orgE<gt>