Adding upstream version 2.25.15.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
This commit is contained in:
parent
10737b110a
commit
b543f2e88d
485 changed files with 191459 additions and 0 deletions
524
lib/Devscripts/Salsa/Config.pm
Executable file
524
lib/Devscripts/Salsa/Config.pm
Executable file
|
@ -0,0 +1,524 @@
|
|||
# Salsa configuration (inherits from Devscripts::Config)
|
||||
package Devscripts::Salsa::Config;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo;
|
||||
|
||||
extends 'Devscripts::Config';
|
||||
|
||||
# Declare accessors for each option
|
||||
# Source : ./lib/Devscripts/Salsa/Config.pm:use constant keys
|
||||
# command & private_token
|
||||
# Skipping: info
|
||||
# Note : [Salsa = GitLab] jobs = builds, info = prompt, token = private_token
|
||||
foreach (qw(
|
||||
command private_token
|
||||
chdir cache_file no_cache path yes no_fail verbose debug
|
||||
user user_id group group_id token token_file
|
||||
all all_archived archived skip skip_file no_skip
|
||||
analytics auto_devops container environments feature_flags forks
|
||||
infrastructure issues jobs lfs monitor mr packages pages releases
|
||||
repo request_access requirements security_compliance service_desk snippets
|
||||
wiki
|
||||
avatar_path desc desc_pattern
|
||||
email disable_email email_recipient
|
||||
irc_channel
|
||||
irker disable_irker irker_host irker_port
|
||||
kgb disable_kgb kgb_options
|
||||
tagpending disable_tagpending
|
||||
rename_head source_branch dest_branch
|
||||
enable_remove_branch disable_remove_branch
|
||||
build_timeout ci_config_path
|
||||
schedule_desc schedule_ref schedule_cron schedule_tz schedule_enable
|
||||
schedule_disable schedule_run schedule_delete
|
||||
mr_allow_squash mr_desc mr_dst_branch mr_dst_project
|
||||
mr_remove_source_branch mr_src_branch mr_src_project mr_title
|
||||
api_url git_server_url irker_server_url kgb_server_url
|
||||
tagpending_server_url
|
||||
)
|
||||
) {
|
||||
has $_ => (is => 'rw');
|
||||
}
|
||||
|
||||
my $cacheDir;
|
||||
|
||||
our @kgbOpt = qw(
|
||||
push_events issues_events confidential_issues_events
|
||||
confidential_comments_events merge_requests_events tag_push_events
|
||||
note_events job_events pipeline_events wiki_page_events
|
||||
confidential_note_events enable_ssl_verification
|
||||
);
|
||||
|
||||
BEGIN {
|
||||
$cacheDir = $ENV{XDG_CACHE_HOME} || $ENV{HOME} . '/.cache';
|
||||
}
|
||||
|
||||
# Options
|
||||
use constant keys => [
|
||||
# General salsa
|
||||
[
|
||||
'C|chdir=s', undef,
|
||||
sub { return (chdir($_[1]) ? 1 : (0, "$_[1] doesn't exist")) }
|
||||
],
|
||||
[
|
||||
'cache-file',
|
||||
'SALSA_CACHE_FILE',
|
||||
sub {
|
||||
$_[0]->cache_file($_[1] ? $_[1] : undef);
|
||||
},
|
||||
"$cacheDir/salsa.json"
|
||||
],
|
||||
[
|
||||
'no-cache',
|
||||
'SALSA_NO_CACHE',
|
||||
sub {
|
||||
$_[0]->cache_file(undef)
|
||||
if ($_[1] !~ /^(?:no|0+)$/i);
|
||||
return 1;
|
||||
}
|
||||
],
|
||||
[
|
||||
'path=s',
|
||||
'SALSA_REPO_PATH',
|
||||
sub {
|
||||
$_ = $_[1];
|
||||
s#/*(.*)/*#$1#;
|
||||
$_[0]->path($_);
|
||||
return /^[\w\d\-]+$/ ? 1 : (0, "Bad path $_");
|
||||
}
|
||||
],
|
||||
|
||||
# Responses
|
||||
['yes!', 'SALSA_YES', sub { info(1, "SALSA_YES", @_) }],
|
||||
['no-fail', 'SALSA_NO_FAIL', 'bool'],
|
||||
|
||||
# Output
|
||||
['verbose!', 'SALSA_VERBOSE', sub { $verbose = 1 }],
|
||||
['debug', undef, sub { $verbose = 2 }],
|
||||
['info|i', 'SALSA_INFO', sub { info(-1, 'SALSA_INFO', @_) }],
|
||||
|
||||
# General GitLab
|
||||
['user=s', 'SALSA_USER', qr/^[\-\w]+$/],
|
||||
['user-id=s', 'SALSA_USER_ID', qr/^\d+$/],
|
||||
['group=s', 'SALSA_GROUP', qr/^[\/\-\w]+$/],
|
||||
['group-id=s', 'SALSA_GROUP_ID', qr/^\d+$/],
|
||||
['token', 'SALSA_TOKEN', sub { $_[0]->private_token($_[1]) }],
|
||||
[
|
||||
'token-file',
|
||||
'SALSA_TOKEN_FILE',
|
||||
sub {
|
||||
my ($self, $v) = @_;
|
||||
return (0, "Unable to open token file") unless (-r $v);
|
||||
open F, $v;
|
||||
my $s = join '', <F>;
|
||||
close F;
|
||||
if ($s
|
||||
=~ m/^[^#]*(?:SALSA_(?:PRIVATE_)?TOKEN)\s*=\s*(["'])?([-\w]+)\1?$/m
|
||||
) {
|
||||
$self->private_token($2);
|
||||
return 1;
|
||||
} else {
|
||||
return (0, "No token found in file $v");
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
# List/search
|
||||
['all'],
|
||||
['all-archived'],
|
||||
['archived!', 'SALSA_ARCHIVED', 'bool', 0],
|
||||
['skip=s', 'SALSA_SKIP', undef, sub { [] }],
|
||||
[
|
||||
'skip-file=s',
|
||||
'SALSA_SKIP_FILE',
|
||||
sub {
|
||||
return 1 unless $_[1];
|
||||
return (0, "Unable to read $_[1]") unless (-r $_[1]);
|
||||
open my $fh, $_[1];
|
||||
push @{ $_[0]->skip }, (map { chomp $_; ($_ ? $_ : ()) } <$fh>);
|
||||
return 1;
|
||||
}
|
||||
],
|
||||
['no-skip', undef, sub { $_[0]->skip([]); $_[0]->skip_file(undef); }],
|
||||
|
||||
# Features
|
||||
[
|
||||
'analytics=s', 'SALSA_ENABLE_ANALYTICS',
|
||||
qr/y(es)?|true|enabled?|private|no?|false|disabled?/
|
||||
],
|
||||
[
|
||||
'auto-devops=s',
|
||||
'SALSA_ENABLE_AUTO_DEVOPS',
|
||||
qr/y(es)?|true|enabled?|1|no?|false|disabled?|0/
|
||||
],
|
||||
[
|
||||
'container=s', 'SALSA_ENABLE_CONTAINER',
|
||||
qr/y(es)?|true|enabled?|private|no?|false|disabled?/
|
||||
],
|
||||
[
|
||||
'environments=s',
|
||||
'SALSA_ENABLE_ENVIRONMENTS',
|
||||
qr/y(es)?|true|enabled?|private|no?|false|disabled?/
|
||||
],
|
||||
[
|
||||
'feature-flags=s',
|
||||
'SALSA_ENABLE_FEATURE_FLAGS',
|
||||
qr/y(es)?|true|enabled?|private|no?|false|disabled?/
|
||||
],
|
||||
[
|
||||
'forks=s', 'SALSA_ENABLE_FORKS',
|
||||
qr/y(es)?|true|enabled?|private|no?|false|disabled?/
|
||||
],
|
||||
[
|
||||
'infrastructure=s',
|
||||
'SALSA_ENABLE_INFRASTRUCTURE',
|
||||
qr/y(es)?|true|enabled?|private|no?|false|disabled?/
|
||||
],
|
||||
[
|
||||
'issues=s', 'SALSA_ENABLE_ISSUES',
|
||||
qr/y(es)?|true|enabled?|private|no?|false|disabled?/
|
||||
],
|
||||
# Renamed terminology, kept for legacy: jobs == builds_access_level (ENABLE_JOBS -> ENABLE_BUILD)
|
||||
[
|
||||
'jobs=s', 'SALSA_ENABLE_JOBS',
|
||||
qr/y(es)?|true|enabled?|private|no?|false|disabled?/
|
||||
],
|
||||
[
|
||||
'lfs=s', 'SALSA_ENABLE_LFS',
|
||||
qr/y(es)?|true|enabled?|1|no?|false|disabled?|0/
|
||||
],
|
||||
[
|
||||
'monitor=s', 'SALSA_ENABLE_MONITOR',
|
||||
qr/y(es)?|true|enabled?|private|no?|false|disabled?/
|
||||
],
|
||||
[
|
||||
'mr=s', 'SALSA_ENABLE_MR',
|
||||
qr/y(es)?|true|enabled?|private|no?|false|disabled?/
|
||||
],
|
||||
[
|
||||
'packages=s', 'SALSA_ENABLE_PACKAGES',
|
||||
qr/y(es)?|true|enabled?|1|no?|false|disabled?|0/
|
||||
],
|
||||
[
|
||||
'pages=s', 'SALSA_ENABLE_PAGES',
|
||||
qr/y(es)?|true|enabled?|private|no?|false|disabled?/
|
||||
],
|
||||
[
|
||||
'releases=s', 'SALSA_ENABLE_RELEASES',
|
||||
qr/y(es)?|true|enabled?|private|no?|false|disabled?/
|
||||
],
|
||||
[
|
||||
'repo=s', 'SALSA_ENABLE_REPO',
|
||||
qr/y(es)?|true|enabled?|private|no?|false|disabled?/
|
||||
],
|
||||
[
|
||||
'request-access=s',
|
||||
'SALSA_REQUEST_ACCESS',
|
||||
qr/y(es)?|true|enabled?|1|no?|false|disabled?|0/
|
||||
],
|
||||
[
|
||||
'requirements=s',
|
||||
'SALSA_ENABLE_REQUIREMENTS',
|
||||
qr/y(es)?|true|enabled?|private|no?|false|disabled?/
|
||||
],
|
||||
[
|
||||
'security-compliance=s',
|
||||
'SALSA_ENABLE_SECURITY_COMPLIANCE',
|
||||
qr/y(es)?|true|enabled?|private|no?|false|disabled?/
|
||||
],
|
||||
[
|
||||
'service-desk=s',
|
||||
'SALSA_ENABLE_SERVICE_DESK',
|
||||
qr/y(es)?|true|enabled?|1|no?|false|disabled?|0/
|
||||
],
|
||||
[
|
||||
'snippets=s', 'SALSA_ENABLE_SNIPPETS',
|
||||
qr/y(es)?|true|enabled?|private|no?|false|disabled?/
|
||||
],
|
||||
[
|
||||
'wiki=s', 'SALSA_ENABLE_WIKI',
|
||||
qr/y(es)?|true|enabled?|private|no?|false|disabled?/
|
||||
],
|
||||
|
||||
# Branding
|
||||
['avatar-path=s', 'SALSA_AVATAR_PATH', undef],
|
||||
['desc!', 'SALSA_DESC', 'bool'],
|
||||
['desc-pattern=s', 'SALSA_DESC_PATTERN', qr/\w/, 'Debian package %p'],
|
||||
|
||||
# Notification
|
||||
[
|
||||
'email!', undef,
|
||||
sub { !$_[1] or $_[0]->enable('yes', 'email', 'disable_email'); }
|
||||
],
|
||||
[
|
||||
'disable-email!', undef,
|
||||
sub { !$_[1] or $_[0]->enable('no', 'email', 'disable_email'); }
|
||||
],
|
||||
[
|
||||
undef, 'SALSA_EMAIL',
|
||||
sub { $_[0]->enable($_[1], 'email', 'disable_email'); }
|
||||
],
|
||||
['email-recipient=s', 'SALSA_EMAIL_RECIPIENTS', undef, sub { [] }],
|
||||
['irc-channel|irc=s', 'SALSA_IRC_CHANNEL', undef, sub { [] }],
|
||||
[
|
||||
'irker!', undef,
|
||||
sub { !$_[1] or $_[0]->enable('yes', 'irker', 'disable_irker'); }
|
||||
],
|
||||
[
|
||||
'disable-irker!', undef,
|
||||
sub { !$_[1] or $_[0]->enable('no', 'irker', 'disable_irker'); }
|
||||
],
|
||||
[
|
||||
undef, 'SALSA_IRKER',
|
||||
sub { $_[0]->enable($_[1], 'irker', 'disable_irker'); }
|
||||
],
|
||||
['irker-host=s', 'SALSA_IRKER_HOST', undef, 'ruprecht.snow-crash.org'],
|
||||
['irker-port=s', 'SALSA_IRKER_PORT', qr/^\d*$/],
|
||||
[
|
||||
'kgb!', undef,
|
||||
sub { !$_[1] or $_[0]->enable('yes', 'kgb', 'disable_kgb'); }
|
||||
],
|
||||
[
|
||||
'disable-kgb!', undef,
|
||||
sub { !$_[1] or $_[0]->enable('no', 'kgb', 'disable_kgb'); }
|
||||
],
|
||||
[undef, 'SALSA_KGB', sub { $_[0]->enable($_[1], 'kgb', 'disable_kgb'); }],
|
||||
[
|
||||
'kgb-options=s',
|
||||
'SALSA_KGB_OPTIONS',
|
||||
qr/\w/,
|
||||
'push_events,issues_events,merge_requests_events,tag_push_events,'
|
||||
. 'note_events,pipeline_events,wiki_page_events,'
|
||||
. 'enable_ssl_verification'
|
||||
],
|
||||
[
|
||||
'tagpending!',
|
||||
undef,
|
||||
sub {
|
||||
!$_[1]
|
||||
or $_[0]->enable('yes', 'tagpending', 'disable_tagpending');
|
||||
}
|
||||
],
|
||||
[
|
||||
'disable-tagpending!',
|
||||
undef,
|
||||
sub {
|
||||
!$_[1] or $_[0]->enable('no', 'tagpending', 'disable_tagpending');
|
||||
}
|
||||
],
|
||||
[
|
||||
undef, 'SALSA_TAGPENDING',
|
||||
sub { $_[0]->enable($_[1], 'tagpending', 'disable_tagpending'); }
|
||||
],
|
||||
|
||||
# Branch
|
||||
['rename-head!', 'SALSA_RENAME_HEAD', 'bool'],
|
||||
['source-branch=s', 'SALSA_SOURCE_BRANCH', undef, 'master'],
|
||||
['dest-branch=s', 'SALSA_DEST_BRANCH', undef, 'debian/latest'],
|
||||
[
|
||||
'enable-remove-source-branch!',
|
||||
undef,
|
||||
sub {
|
||||
!$_[1]
|
||||
or $_[0]
|
||||
->enable('yes', 'enable_remove_branch', 'disable_remove_branch');
|
||||
}
|
||||
],
|
||||
[
|
||||
'disable-remove-source-branch!',
|
||||
undef,
|
||||
sub {
|
||||
!$_[1]
|
||||
or $_[0]
|
||||
->enable('no', 'enable_remove_branch', 'disable_remove_branch');
|
||||
}
|
||||
],
|
||||
[
|
||||
undef,
|
||||
'SALSA_REMOVE_SOURCE_BRANCH',
|
||||
sub {
|
||||
$_[0]
|
||||
->enable($_[1], 'enable_remove_branch', 'disable_remove_branch');
|
||||
}
|
||||
],
|
||||
|
||||
# Merge requests
|
||||
['mr-allow-squash!', 'SALSA_MR_ALLOW_SQUASH', 'bool', 1],
|
||||
['mr-desc=s'],
|
||||
['mr-dst-branch=s', undef, undef, 'master'],
|
||||
['mr-dst-project=s'],
|
||||
['mr-remove-source-branch!', 'SALSA_MR_REMOVE_SOURCE_BRANCH', 'bool', 0],
|
||||
['mr-src-branch=s'],
|
||||
['mr-src-project=s'],
|
||||
['mr-title=s'],
|
||||
|
||||
# CI
|
||||
['build-timeout=s', 'SALSA_BUILD_TIMEOUT', qr/^\d+$/, '3600'],
|
||||
['ci-config-path=s', 'SALSA_CI_CONFIG_PATH', qr/\./],
|
||||
|
||||
# Pipeline schedules
|
||||
['schedule-desc=s', 'SALSA_SCHEDULE_DESC', qr/\w/],
|
||||
['schedule-ref=s', 'SALSA_SCHEDULE_REF'],
|
||||
['schedule-cron=s', 'SALSA_SCHEDULE_CRON'],
|
||||
['schedule-tz=s', 'SALSA_SCHEDULE_TZ'],
|
||||
['schedule-enable!', 'SALSA_SCHEDULE_ENABLE', 'bool'],
|
||||
['schedule-disable!', 'SALSA_SCHEDULE_DISABLE', 'bool'],
|
||||
['schedule-run!', 'SALSA_SCHEDULE_RUN', 'bool'],
|
||||
['schedule-delete!', 'SALSA_SCHEDULE_DELETE', 'bool'],
|
||||
|
||||
# Manage other GitLab instances
|
||||
[
|
||||
'api-url=s', 'SALSA_API_URL',
|
||||
qr#^https?://#, 'https://salsa.debian.org/api/v4'
|
||||
],
|
||||
[
|
||||
'git-server-url=s', 'SALSA_GIT_SERVER_URL',
|
||||
qr/^\S+\@\S+/, 'git@salsa.debian.org:'
|
||||
],
|
||||
[
|
||||
'irker-server-url=s', 'SALSA_IRKER_SERVER_URL',
|
||||
qr'^ircs?://', 'ircs://irc.oftc.net:6697/'
|
||||
],
|
||||
[
|
||||
'kgb-server-url=s', 'SALSA_KGB_SERVER_URL',
|
||||
qr'^https?://', 'https://kgb.debian.net/webhook/?channel='
|
||||
],
|
||||
[
|
||||
'tagpending-server-url=s',
|
||||
'SALSA_TAGPENDING_SERVER_URL',
|
||||
qr'^https?://',
|
||||
'https://webhook.salsa.debian.org/tagpending/'
|
||||
],
|
||||
];
|
||||
|
||||
# Consistency rules
|
||||
use constant rules => [
|
||||
# Reject unless token exists
|
||||
sub {
|
||||
return (1,
|
||||
"SALSA_TOKEN not set in configuration files. Some commands may fail"
|
||||
) unless ($_[0]->private_token);
|
||||
},
|
||||
# Get command
|
||||
sub {
|
||||
return (0, "No command given, aborting") unless (@ARGV);
|
||||
$_[0]->command(shift @ARGV);
|
||||
return (0, "Malformed command: " . $_[0]->command)
|
||||
unless ($_[0]->command =~ /^[a-z_]+$/);
|
||||
return 1;
|
||||
},
|
||||
sub {
|
||||
if ( ($_[0]->group or $_[0]->group_id)
|
||||
and ($_[0]->user_id or $_[0]->user)) {
|
||||
ds_warn "Both --user-id and --group-id are set, ignore --group-id";
|
||||
$_[0]->group(undef);
|
||||
$_[0]->group_id(undef);
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
sub {
|
||||
if ($_[0]->group and $_[0]->group_id) {
|
||||
ds_warn "Both --group-id and --group are set, ignore --group";
|
||||
$_[0]->group(undef);
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
sub {
|
||||
if ($_[0]->user and $_[0]->user_id) {
|
||||
ds_warn "Both --user-id and --user are set, ignore --user";
|
||||
$_[0]->user(undef);
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
sub {
|
||||
if ($_[0]->email and not @{ $_[0]->email_recipient }) {
|
||||
return (0, '--email-recipient needed with --email');
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
sub {
|
||||
if (@{ $_[0]->irc_channel }) {
|
||||
foreach (@{ $_[0]->irc_channel }) {
|
||||
if (/^#/) {
|
||||
return (1,
|
||||
"# found in --irc-channel, assuming double hash is wanted"
|
||||
);
|
||||
}
|
||||
}
|
||||
if ($_[0]->irc_channel->[1] and $_[0]->kgb) {
|
||||
return (0, "Only one IRC channel is accepted with --kgb");
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
},
|
||||
sub {
|
||||
$_[0]->kgb_options([sort split ',\s*', $_[0]->kgb_options]);
|
||||
my @err;
|
||||
foreach my $o (@{ $_[0]->kgb_options }) {
|
||||
unless (grep { $_ eq $o } @kgbOpt) {
|
||||
push @err, $o;
|
||||
}
|
||||
}
|
||||
return (0, "Unknown KGB options: " . join(', ', @err))
|
||||
if @err;
|
||||
return 1;
|
||||
},
|
||||
];
|
||||
|
||||
sub usage {
|
||||
# Source: ./scripts/salsa.pl:=head1 SYNOPSIS
|
||||
# ./lib/Devscripts/Salsa.pm:sub run -> $ ls ./lib/Devscripts/Salsa/*.pm
|
||||
print <<END;
|
||||
usage: salsa <command> <parameters> <options>
|
||||
|
||||
Most used commands for managing users and groups:
|
||||
- add_user : Add a user to a group
|
||||
- delete_user : Remove a user from a group
|
||||
- search_groups : Search for a group using given string
|
||||
- search_users : Search for a user using given string
|
||||
- update_user : Update a user's role in a group
|
||||
- whoami : Gives information on the token owner
|
||||
|
||||
Most used commands for managing repositories:
|
||||
- checkout : Clone a project's repository in current directory
|
||||
- fork : Fork a project
|
||||
- last_ci_status : Displays the last continuous integration result
|
||||
- mr : Creates a merge request
|
||||
- schedules : Lists current pipeline schedule items
|
||||
- push_repo : Push local git repository to upstream repository
|
||||
- search_projects: Search for a project using given string
|
||||
- update_projects: Configure project(s) configuration
|
||||
- update_safe : Shows differences before running update_projects
|
||||
|
||||
See salsa(1) manpage for more.
|
||||
END
|
||||
}
|
||||
|
||||
sub info {
|
||||
my ($num, $key, undef, $nv) = @_;
|
||||
$nv = (
|
||||
$nv =~ /^yes|1$/ ? $num
|
||||
: $nv =~ /^no|0$/i ? 0
|
||||
: return (0, "Bad $key value"));
|
||||
$ds_yes = $nv;
|
||||
}
|
||||
|
||||
sub enable {
|
||||
my ($self, $v, $en, $dis) = @_;
|
||||
$v = lc($v);
|
||||
if ($v eq 'ignore') {
|
||||
$self->{$en} = $self->{$dis} = 0;
|
||||
} elsif ($v eq 'yes') {
|
||||
$self->{$en} = 1;
|
||||
$self->{$dis} = 0;
|
||||
} elsif ($v eq 'no') {
|
||||
$self->{$en} = 0;
|
||||
$self->{$dis} = 1;
|
||||
} else {
|
||||
return (0, "Bad value for SALSA_" . uc($en));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
314
lib/Devscripts/Salsa/Hooks.pm
Normal file
314
lib/Devscripts/Salsa/Hooks.pm
Normal file
|
@ -0,0 +1,314 @@
|
|||
# Common hooks library
|
||||
package Devscripts::Salsa::Hooks;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
sub add_hooks {
|
||||
my ($self, $repo_id, $repo) = @_;
|
||||
if ( $self->config->kgb
|
||||
or $self->config->disable_kgb
|
||||
or $self->config->tagpending
|
||||
or $self->config->disable_tagpending
|
||||
or $self->config->irker
|
||||
or $self->config->disable_irker
|
||||
or $self->config->email
|
||||
or $self->config->disable_email) {
|
||||
my $hooks = $self->enabled_hooks($repo_id);
|
||||
return 1 unless (defined $hooks);
|
||||
# KGB hook (IRC)
|
||||
if ($self->config->kgb or $self->config->disable_kgb) {
|
||||
unless ($self->config->irc_channel->[0]
|
||||
or $self->config->disable_kgb) {
|
||||
ds_warn "--kgb needs --irc-channel";
|
||||
return 1;
|
||||
}
|
||||
if ($self->config->irc_channel->[1]) {
|
||||
ds_warn "KGB accepts only one --irc-channel value,";
|
||||
}
|
||||
if ($hooks->{kgb}) {
|
||||
ds_warn "Deleting old kgb (was $hooks->{kgb}->{url})";
|
||||
$self->api->delete_project_hook($repo_id, $hooks->{kgb}->{id});
|
||||
}
|
||||
if ($self->config->irc_channel->[0]
|
||||
and not $self->config->disable_kgb) {
|
||||
# TODO: if useful, add parameters for this options
|
||||
eval {
|
||||
$self->api->create_project_hook(
|
||||
$repo_id,
|
||||
{
|
||||
url => $self->config->kgb_server_url
|
||||
. $self->config->irc_channel->[0],
|
||||
map { ($_ => 1) } @{ $self->config->kgb_options },
|
||||
});
|
||||
ds_verbose "KGB hook added to project $repo_id (channel: "
|
||||
. $self->config->irc_channel->[0] . ')';
|
||||
};
|
||||
if ($@) {
|
||||
ds_warn "Fail to add KGB hook: $@";
|
||||
if (!$self->config->no_fail) {
|
||||
ds_verbose "Use --no-fail to continue";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# Irker hook (IRC)
|
||||
if ($self->config->irker or $self->config->disable_irker) {
|
||||
unless ($self->config->irc_channel->[0]
|
||||
or $self->config->disable_irker) {
|
||||
ds_warn "--irker needs --irc-channel";
|
||||
return 1;
|
||||
}
|
||||
if ($hooks->{irker}) {
|
||||
no warnings;
|
||||
ds_warn
|
||||
"Deleting old irker (redirected to $hooks->{irker}->{recipients})";
|
||||
$self->api->delete_project_service($repo_id, 'irker');
|
||||
}
|
||||
if ($self->config->irc_channel->[0]
|
||||
and not $self->config->disable_irker) {
|
||||
# TODO: if useful, add parameters for this options
|
||||
my $ch = join(' ',
|
||||
map { '#' . $_ } @{ $self->config->irc_channel });
|
||||
$self->api->edit_project_service(
|
||||
$repo_id, 'irker',
|
||||
{
|
||||
active => 1,
|
||||
server_host => $self->config->irker_host,
|
||||
(
|
||||
$self->config->irker_port
|
||||
? (server_port => $self->config->irker_port)
|
||||
: ()
|
||||
),
|
||||
default_irc_uri => $self->config->irker_server_url,
|
||||
recipients => $ch,
|
||||
colorize_messages => 1,
|
||||
});
|
||||
ds_verbose
|
||||
"Irker hook added to project $repo_id (channel: $ch)";
|
||||
}
|
||||
}
|
||||
# email on push
|
||||
if ($self->config->email or $self->config->disable_email) {
|
||||
if ($hooks->{email}) {
|
||||
no warnings;
|
||||
ds_warn
|
||||
"Deleting old email-on-push (redirected to $hooks->{email}->{recipients})";
|
||||
$self->api->delete_project_service($repo_id, 'emails-on-push');
|
||||
}
|
||||
if (@{ $self->config->email_recipient }
|
||||
and not $self->config->disable_email) {
|
||||
# TODO: if useful, add parameters for this options
|
||||
$self->api->edit_project_service(
|
||||
$repo_id,
|
||||
'emails-on-push',
|
||||
{
|
||||
recipients => join(' ',
|
||||
map { my $a = $_; $a =~ s/%p/$repo/; $a }
|
||||
@{ $self->config->email_recipient }),
|
||||
});
|
||||
no warnings;
|
||||
ds_verbose
|
||||
"Email-on-push hook added to project $repo_id (recipients: "
|
||||
. join(' ', @{ $self->config->email_recipient }) . ')';
|
||||
}
|
||||
}
|
||||
# Tagpending hook
|
||||
if ($self->config->tagpending or $self->config->disable_tagpending) {
|
||||
if ($hooks->{tagpending}) {
|
||||
ds_warn
|
||||
"Deleting old tagpending (was $hooks->{tagpending}->{url})";
|
||||
$self->api->delete_project_hook($repo_id,
|
||||
$hooks->{tagpending}->{id});
|
||||
}
|
||||
my $repo_name = $self->api->project($repo_id)->{name};
|
||||
unless ($self->config->disable_tagpending) {
|
||||
eval {
|
||||
$self->api->create_project_hook(
|
||||
$repo_id,
|
||||
{
|
||||
url => $self->config->tagpending_server_url
|
||||
. $repo_name,
|
||||
push_events => 1,
|
||||
});
|
||||
ds_verbose "Tagpending hook added to project $repo_id";
|
||||
};
|
||||
if ($@) {
|
||||
ds_warn "Fail to add Tagpending hook: $@";
|
||||
if (!$self->config->no_fail) {
|
||||
ds_verbose "Use --no-fail to continue";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub enabled_hooks {
|
||||
my ($self, $repo_id) = @_;
|
||||
my $hooks;
|
||||
my $res = {};
|
||||
if ( $self->config->kgb
|
||||
or $self->config->disable_kgb
|
||||
or $self->config->tagpending
|
||||
or $self->config->disable_tagpending) {
|
||||
$hooks = eval { $self->api->project_hooks($repo_id) };
|
||||
if ($@) {
|
||||
ds_warn "Unable to check hooks for project $repo_id";
|
||||
return undef;
|
||||
}
|
||||
foreach my $h (@{$hooks}) {
|
||||
$res->{kgb} = {
|
||||
id => $h->{id},
|
||||
url => $h->{url},
|
||||
options => [grep { $h->{$_} and $h->{$_} eq 1 } keys %$h],
|
||||
}
|
||||
if $h->{url} =~ /\Q$self->{config}->{kgb_server_url}\E/;
|
||||
$res->{tagpending} = {
|
||||
id => $h->{id},
|
||||
url => $h->{url},
|
||||
}
|
||||
if $h->{url} =~ /\Q$self->{config}->{tagpending_server_url}\E/;
|
||||
}
|
||||
}
|
||||
if ( ($self->config->email or $self->config->disable_email)
|
||||
and $_ = $self->api->project_service($repo_id, 'emails-on-push')
|
||||
and $_->{active}) {
|
||||
$res->{email} = $_->{properties};
|
||||
}
|
||||
if ( ($self->config->irker or $self->config->disable_irker)
|
||||
and $_ = $self->api->project_service($repo_id, 'irker')
|
||||
and $_->{active}) {
|
||||
$res->{irker} = $_->{properties};
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
sub _check_config {
|
||||
my ($config, $key_name, $config_name, $can_be_private, $res_ref) = @_;
|
||||
if (!$config) { return undef; }
|
||||
for ($config) {
|
||||
if ($can_be_private) {
|
||||
if ($_ eq "private") {
|
||||
push @$res_ref, $key_name => "private";
|
||||
} elsif ($_ =~ qr/y(es)?|true|enabled?/) {
|
||||
push @$res_ref, $key_name => "enabled";
|
||||
} elsif ($_ =~ qr/no?|false|disabled?/) {
|
||||
push @$res_ref, $key_name => "disabled";
|
||||
} else {
|
||||
print "error with SALSA_$config_name";
|
||||
}
|
||||
} else {
|
||||
if ($_ =~ qr/y(es)?|true|enabled?/) {
|
||||
push @$res_ref, $key_name => 1;
|
||||
} elsif ($_ =~ qr/no?|false|disabled?/) {
|
||||
push @$res_ref, $key_name => 0;
|
||||
} else {
|
||||
print "error with SALSA_$config_name";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub desc {
|
||||
my ($self, $repo) = @_;
|
||||
my @res = ();
|
||||
if ($self->config->desc) {
|
||||
my $str = $self->config->desc_pattern;
|
||||
$str =~ s/%P/$repo/g;
|
||||
$repo =~ s#.*/##;
|
||||
$str =~ s/%p/$repo/g;
|
||||
push @res, description => $str;
|
||||
}
|
||||
if ($self->config->build_timeout) {
|
||||
push @res, build_timeout => $self->config->build_timeout;
|
||||
}
|
||||
if ($self->config->ci_config_path) {
|
||||
push @res, ci_config_path => $self->config->ci_config_path;
|
||||
}
|
||||
|
||||
# Parameter: config value, key name, config name, has private
|
||||
_check_config($self->config->analytics,
|
||||
"analytics_access_level", "ENABLE_ANALYTICS", 1, \@res);
|
||||
_check_config($self->config->auto_devops,
|
||||
"auto_devops_enabled", "ENABLE_AUTO_DEVOPS", 0, \@res);
|
||||
_check_config(
|
||||
$self->config->container,
|
||||
"container_registry_access_level",
|
||||
"ENABLE_CONTAINER", 1, \@res
|
||||
);
|
||||
_check_config($self->config->environments,
|
||||
"environments_access_level", "ENABLE_ENVIRONMENTS", 1, \@res);
|
||||
_check_config($self->config->feature_flags,
|
||||
"feature_flags_access_level", "ENABLE_FEATURE_FLAGS", 1, \@res);
|
||||
_check_config($self->config->forks, "forking_access_level",
|
||||
"ENABLE_FORKS", 1, \@res);
|
||||
_check_config($self->config->infrastructure,
|
||||
"infrastructure_access_level", "ENABLE_INFRASTRUCTURE", 1, \@res);
|
||||
_check_config($self->config->issues, "issues_access_level",
|
||||
"ENABLE_ISSUES", 1, \@res);
|
||||
# Renamed terminology, kept for legacy: jobs == builds_access_level (ENABLE_JOBS -> ENABLE_BUILD)
|
||||
_check_config($self->config->jobs, "builds_access_level", "ENABLE_JOBS",
|
||||
1, \@res);
|
||||
_check_config($self->config->lfs, "lfs_enabled", "ENABLE_LFS", 0, \@res);
|
||||
_check_config($self->config->mr, "merge_requests_access_level",
|
||||
"ENABLE_MR", 1, \@res);
|
||||
_check_config($self->config->monitor,
|
||||
"monitor_access_level", "ENABLE_MONITOR", 1, \@res);
|
||||
_check_config($self->config->packages,
|
||||
"packages_enabled", "ENABLE_PACKAGES", 0, \@res);
|
||||
_check_config($self->config->pages, "pages_access_level", "ENABLE_PAGES",
|
||||
1, \@res);
|
||||
_check_config($self->config->releases,
|
||||
"releases_access_level", "ENABLE_RELEASES", 1, \@res);
|
||||
_check_config(
|
||||
$self->config->disable_remove_branch,
|
||||
"remove_source_branch_after_merge",
|
||||
"REMOVE_SOURCE_BRANCH", 0, \@res
|
||||
);
|
||||
_check_config($self->config->repo, "repository_access_level",
|
||||
"ENABLE_REPO", 1, \@res);
|
||||
_check_config($self->config->request_access,
|
||||
"request_access_enabled", "REQUEST_ACCESS", 0, \@res);
|
||||
_check_config($self->config->requirements,
|
||||
"requirements_access_level", "ENABLE_REQUIREMENTS", 1, \@res);
|
||||
_check_config(
|
||||
$self->config->security_compliance,
|
||||
"security_and_compliance_access_level",
|
||||
"ENABLE_SECURITY_COMPLIANCE", 1, \@res
|
||||
);
|
||||
_check_config($self->config->service_desk,
|
||||
"service_desk_enabled", "ENABLE_SERVICE_DESK", 0, \@res);
|
||||
_check_config($self->config->snippets,
|
||||
"snippets_access_level", "ENABLE_SNIPPETS", 1, \@res);
|
||||
_check_config($self->config->wiki, "wiki_access_level", "ENABLE_WIKI", 1,
|
||||
\@res);
|
||||
|
||||
return @res;
|
||||
}
|
||||
|
||||
sub desc_multipart {
|
||||
my ($self, $repo) = @_;
|
||||
my @res = ();
|
||||
if ($self->config->avatar_path) {
|
||||
my $str = $self->config->avatar_path;
|
||||
$str =~ s/%p/$repo/g;
|
||||
unless (-r $str) {
|
||||
ds_warn "Unable to find: $str";
|
||||
unless ($self->config->no_fail) {
|
||||
ds_verbose "Use --no-fail to continue";
|
||||
exit 1;
|
||||
}
|
||||
} else {
|
||||
# avatar_path (salsa) -> avatar (GitLab API)
|
||||
push @res, avatar => $str;
|
||||
}
|
||||
}
|
||||
return @res;
|
||||
}
|
||||
|
||||
1;
|
75
lib/Devscripts/Salsa/Repo.pm
Executable file
75
lib/Devscripts/Salsa/Repo.pm
Executable file
|
@ -0,0 +1,75 @@
|
|||
# Common method to get projects
|
||||
package Devscripts::Salsa::Repo;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
with "Devscripts::Salsa::Hooks";
|
||||
|
||||
sub get_repo {
|
||||
my ($self, $prompt, @reponames) = @_;
|
||||
my @repos;
|
||||
if (($self->config->all or $self->config->all_archived)
|
||||
and @reponames == 0) {
|
||||
ds_debug "--all is set";
|
||||
my $options = {};
|
||||
$options->{order_by} = 'name';
|
||||
$options->{sort} = 'asc';
|
||||
$options->{archived} = 'false' if not $self->config->all_archived;
|
||||
$options->{with_shared}
|
||||
= 'false'; # do not operate on foreign projects shared with us
|
||||
my $projects;
|
||||
# This rule disallow trying to configure all "Debian" projects:
|
||||
# - Debian id is 2
|
||||
# - next is 1987
|
||||
if ($self->group_id) {
|
||||
$projects
|
||||
= $self->api->paginator('group_projects', $self->group_id,
|
||||
$options)->all;
|
||||
} elsif ($self->user_id) {
|
||||
$projects
|
||||
= $self->api->paginator('user_projects', $self->user_id,
|
||||
$options)->all;
|
||||
} else {
|
||||
ds_warn "Missing or invalid token";
|
||||
return 1;
|
||||
}
|
||||
unless ($projects) {
|
||||
ds_warn "No projects found";
|
||||
return 1;
|
||||
}
|
||||
@repos = map {
|
||||
$self->projectCache->{ $_->{path_with_namespace} } = $_->{id};
|
||||
[$_->{id}, $_->{path}]
|
||||
} @$projects;
|
||||
if (@{ $self->config->skip }) {
|
||||
@repos = map {
|
||||
my $res = 1;
|
||||
foreach my $k (@{ $self->config->skip }) {
|
||||
$res = 0 if ($_->[1] =~ m#(?:.*/)?\Q$k\E#);
|
||||
}
|
||||
$res ? $_ : ();
|
||||
} @repos;
|
||||
}
|
||||
if ($ds_yes > 0 or !$prompt) {
|
||||
ds_verbose "Found " . @repos . " projects";
|
||||
} else {
|
||||
unless (
|
||||
ds_prompt(
|
||||
"You're going to configure "
|
||||
. @repos
|
||||
. " projects. Continue (N/y) "
|
||||
) =~ accept
|
||||
) {
|
||||
ds_warn "Aborting";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@repos = map { [$self->project2id($_), $_] } @reponames;
|
||||
}
|
||||
return @repos;
|
||||
}
|
||||
|
||||
1;
|
40
lib/Devscripts/Salsa/add_user.pm
Normal file
40
lib/Devscripts/Salsa/add_user.pm
Normal file
|
@ -0,0 +1,40 @@
|
|||
# Adds a user in a group with a role
|
||||
package Devscripts::Salsa::add_user;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
sub add_user {
|
||||
my ($self, $level, $user) = @_;
|
||||
unless ($level and $user) {
|
||||
ds_warn "Usage $0 --group-id 1234 add_user <level> <userid>";
|
||||
return 1;
|
||||
}
|
||||
unless ($self->group_id) {
|
||||
ds_warn "Unable to add user without --group or --group-id";
|
||||
return 1;
|
||||
}
|
||||
|
||||
my $id = $self->username2id($user) or return 1;
|
||||
my $al = $self->levels_name($level) or return 1;
|
||||
return 1
|
||||
if (
|
||||
$ds_yes < 0
|
||||
and ds_prompt(
|
||||
"You're going to accept $user as $level in group $self->{group_id}. Continue (Y/n) "
|
||||
) =~ refuse
|
||||
);
|
||||
$self->api->add_group_member(
|
||||
$self->group_id,
|
||||
{
|
||||
user_id => $id,
|
||||
access_level => $al,
|
||||
});
|
||||
ds_warn "User $user added to group "
|
||||
. $self->group_id
|
||||
. " with role $level";
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
224
lib/Devscripts/Salsa/check_repo.pm
Executable file
224
lib/Devscripts/Salsa/check_repo.pm
Executable file
|
@ -0,0 +1,224 @@
|
|||
# Parses repo to check if parameters are well set
|
||||
package Devscripts::Salsa::check_repo;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Digest::MD5 qw(md5_hex);
|
||||
use Digest::file qw(digest_file_hex);
|
||||
use LWP::UserAgent;
|
||||
use Moo::Role;
|
||||
|
||||
with "Devscripts::Salsa::Repo";
|
||||
|
||||
sub check_repo {
|
||||
my $self = shift;
|
||||
my ($res) = $self->_check_repo(@_);
|
||||
return $res;
|
||||
}
|
||||
|
||||
sub _url_md5_hex {
|
||||
my $url = shift;
|
||||
my $ua = LWP::UserAgent->new;
|
||||
my $res = $ua->get($url, "User-Agent" => "Devscripts/2.22.3",);
|
||||
if (!$res->is_success) {
|
||||
return undef;
|
||||
}
|
||||
return Digest::MD5::md5_hex($res->content);
|
||||
}
|
||||
|
||||
sub _check_repo {
|
||||
my ($self, @reponames) = @_;
|
||||
my $res = 0;
|
||||
my @fail;
|
||||
unless (@reponames or $self->config->all or $self->config->all_archived) {
|
||||
ds_warn "Usage $0 check_repo <--all|--all-archived|names>";
|
||||
return 1;
|
||||
}
|
||||
if (@reponames and $self->config->all) {
|
||||
ds_warn "--all with a reponame makes no sense";
|
||||
return 1;
|
||||
}
|
||||
if (@reponames and $self->config->all_archived) {
|
||||
ds_warn "--all-archived with a reponame makes no sense";
|
||||
return 1;
|
||||
}
|
||||
# Get repo list from Devscripts::Salsa::Repo
|
||||
my @repos = $self->get_repo(0, @reponames);
|
||||
return @repos unless (ref $repos[0]);
|
||||
foreach my $repo (@repos) {
|
||||
my @err;
|
||||
my ($id, $name) = @$repo;
|
||||
my $project = eval { $self->api->project($id) };
|
||||
unless ($project) {
|
||||
ds_debug $@;
|
||||
ds_warn "Project $name not found";
|
||||
next;
|
||||
}
|
||||
ds_debug "Checking $name ($id)";
|
||||
# check description
|
||||
my %prms = $self->desc($name);
|
||||
my %prms_multipart = $self->desc_multipart($name);
|
||||
if ($self->config->desc) {
|
||||
$project->{description} //= '';
|
||||
push @err, "bad description: $project->{description}"
|
||||
if ($prms{description} ne $project->{description});
|
||||
}
|
||||
# check build timeout
|
||||
if ($self->config->desc) {
|
||||
$project->{build_timeout} //= '';
|
||||
push @err, "bad build_timeout: $project->{build_timeout}"
|
||||
if ($prms{build_timeout} ne $project->{build_timeout});
|
||||
}
|
||||
# check features (w/permission) & ci config
|
||||
foreach (qw(
|
||||
analytics_access_level
|
||||
auto_devops_enabled
|
||||
builds_access_level
|
||||
ci_config_path
|
||||
container_registry_access_level
|
||||
environments_access_level
|
||||
feature_flags_access_level
|
||||
forking_access_level
|
||||
infrastructure_access_level
|
||||
issues_access_level
|
||||
lfs_enabled
|
||||
merge_requests_access_level
|
||||
monitor_access_level
|
||||
packages_enabled
|
||||
pages_access_level
|
||||
releases_access_level
|
||||
remove_source_branch_after_merge
|
||||
repository_access_level
|
||||
request_access_enabled
|
||||
requirements_access_level
|
||||
security_and_compliance_access_level
|
||||
service_desk_enabled
|
||||
snippets_access_level
|
||||
wiki_access_level
|
||||
)
|
||||
) {
|
||||
my $helptext = '';
|
||||
$helptext = ' (enabled)'
|
||||
if (defined $prms{$_} and $prms{$_} eq 1);
|
||||
$helptext = ' (disabled)'
|
||||
if (defined $prms{$_} and $prms{$_} eq 0);
|
||||
push @err, "$_ should be $prms{$_}$helptext"
|
||||
if (defined $prms{$_}
|
||||
and (!defined($project->{$_}) or $project->{$_} ne $prms{$_}));
|
||||
}
|
||||
# only public projects are accepted
|
||||
push @err, "Project visibility: $project->{visibility}"
|
||||
unless ($project->{visibility} eq "public");
|
||||
# Default branch
|
||||
if ($self->config->rename_head) {
|
||||
push @err, "Default branch: $project->{default_branch}"
|
||||
if ($project->{default_branch} ne $self->config->dest_branch);
|
||||
}
|
||||
# Webhooks (from Devscripts::Salsa::Hooks)
|
||||
my $hooks = $self->enabled_hooks($id);
|
||||
unless (defined $hooks) {
|
||||
ds_warn "Unable to get $name hooks";
|
||||
next;
|
||||
}
|
||||
# check avatar's path
|
||||
if ($self->config->avatar_path) {
|
||||
my ($md5_file, $md5_url) = "";
|
||||
if ($prms_multipart{avatar}) {
|
||||
ds_verbose "Calculating local avatar checksum";
|
||||
$md5_file = digest_file_hex($prms_multipart{avatar}, "MD5")
|
||||
or die "$prms_multipart{avatar} failed md5: $!";
|
||||
if ( $project->{avatar_url}
|
||||
and $project->{visibility} eq "public") {
|
||||
ds_verbose "Calculating remote avatar checksum";
|
||||
$md5_url = _url_md5_hex($project->{avatar_url})
|
||||
or die "$project->{avatar_url} failed md5: $!";
|
||||
# Will always force avatar if it can't detect
|
||||
} elsif ($project->{avatar_url}) {
|
||||
ds_warn
|
||||
"$name has an avatar, but is set to $project->{visibility} project visibility thus unable to remotely check checksum";
|
||||
}
|
||||
push @err, "Will set the avatar to be: $prms_multipart{avatar}"
|
||||
if (not length $md5_url or $md5_file ne $md5_url);
|
||||
}
|
||||
}
|
||||
# KGB
|
||||
if ($self->config->kgb and not $hooks->{kgb}) {
|
||||
push @err, "kgb missing";
|
||||
} elsif ($self->config->disable_kgb and $hooks->{kgb}) {
|
||||
push @err, "kgb enabled";
|
||||
} elsif ($self->config->kgb) {
|
||||
push @err,
|
||||
"bad irc channel: "
|
||||
. substr($hooks->{kgb}->{url},
|
||||
length($self->config->kgb_server_url))
|
||||
if $hooks->{kgb}->{url} ne $self->config->kgb_server_url
|
||||
. $self->config->irc_channel->[0];
|
||||
my @wopts = @{ $self->config->kgb_options };
|
||||
my @gopts = sort @{ $hooks->{kgb}->{options} };
|
||||
my $i = 0;
|
||||
while (@gopts and @wopts) {
|
||||
my $a;
|
||||
$a = ($wopts[0] cmp $gopts[0]);
|
||||
if ($a == -1) {
|
||||
push @err, "Missing KGB option " . shift(@wopts);
|
||||
} elsif ($a == 1) {
|
||||
push @err, 'Unwanted KGB option ' . shift(@gopts);
|
||||
} else {
|
||||
shift @wopts;
|
||||
shift @gopts;
|
||||
}
|
||||
}
|
||||
push @err, map { "Missing KGB option $_" } @wopts;
|
||||
push @err, map { "Unwanted KGB option $_" } @gopts;
|
||||
}
|
||||
# Email-on-push
|
||||
if ($self->config->email
|
||||
and not($hooks->{email} and %{ $hooks->{email} })) {
|
||||
push @err, "email-on-push missing";
|
||||
} elsif (
|
||||
$self->config->email
|
||||
and $hooks->{email}->{recipients} ne join(
|
||||
' ',
|
||||
map {
|
||||
my $a = $_;
|
||||
my $b = $name;
|
||||
$b =~ s#.*/##;
|
||||
$a =~ s/%p/$b/;
|
||||
$a
|
||||
} @{ $self->config->email_recipient })
|
||||
) {
|
||||
push @err, "bad email recipients " . $hooks->{email}->{recipients};
|
||||
} elsif ($self->config->disable_email and $hooks->{kgb}) {
|
||||
push @err, "email-on-push enabled";
|
||||
}
|
||||
# Irker
|
||||
if ($self->config->irker and not $hooks->{irker}) {
|
||||
push @err, "irker missing";
|
||||
} elsif ($self->config->irker
|
||||
and $hooks->{irker}->{recipients} ne
|
||||
join(' ', map { "#$_" } @{ $self->config->irc_channel })) {
|
||||
push @err, "bad irc channel: " . $hooks->{irker}->{recipients};
|
||||
} elsif ($self->config->disable_irker and $hooks->{irker}) {
|
||||
push @err, "irker enabled";
|
||||
}
|
||||
# Tagpending
|
||||
if ($self->config->tagpending and not $hooks->{tagpending}) {
|
||||
push @err, "tagpending missing";
|
||||
} elsif ($self->config->disable_tagpending
|
||||
and $hooks->{tagpending}) {
|
||||
push @err, "tagpending enabled";
|
||||
}
|
||||
# report errors
|
||||
if (@err) {
|
||||
$res++;
|
||||
push @fail, $name;
|
||||
print "$name:\n";
|
||||
print "\t$_\n" foreach (@err);
|
||||
} else {
|
||||
ds_verbose "$name: OK";
|
||||
}
|
||||
}
|
||||
return ($res, \@fail);
|
||||
}
|
||||
|
||||
1;
|
81
lib/Devscripts/Salsa/checkout.pm
Normal file
81
lib/Devscripts/Salsa/checkout.pm
Normal file
|
@ -0,0 +1,81 @@
|
|||
# Clones or updates a project's repository using gbp
|
||||
# TODO: git-dpm ?
|
||||
package Devscripts::Salsa::checkout;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Devscripts::Utils;
|
||||
use Dpkg::IPC;
|
||||
use Moo::Role;
|
||||
|
||||
with "Devscripts::Salsa::Repo";
|
||||
|
||||
sub checkout {
|
||||
my ($self, @repos) = @_;
|
||||
unless (@repos or $self->config->all or $self->config->all_archived) {
|
||||
ds_warn "Usage $0 checkout <--all|--all-archived|names>";
|
||||
return 1;
|
||||
}
|
||||
if (@repos and $self->config->all) {
|
||||
ds_warn "--all with a project name makes no sense";
|
||||
return 1;
|
||||
}
|
||||
if (@repos and $self->config->all_archived) {
|
||||
ds_warn "--all-archived with a project name makes no sense";
|
||||
return 1;
|
||||
}
|
||||
# If --all is asked, launch all projects
|
||||
@repos = map { $_->[1] } $self->get_repo(0, @repos) unless (@repos);
|
||||
my $cdir = `pwd`;
|
||||
chomp $cdir;
|
||||
my $res = 0;
|
||||
foreach (@repos) {
|
||||
my $path = $self->project2path($_);
|
||||
s#.*/##;
|
||||
s#^https://salsa.debian.org/##;
|
||||
s#\.git$##;
|
||||
if (-d $_) {
|
||||
chdir $_;
|
||||
ds_verbose "Updating existing checkout in $_";
|
||||
spawn(
|
||||
exec => ['gbp', 'pull', '--pristine-tar'],
|
||||
wait_child => 1,
|
||||
nocheck => 1,
|
||||
);
|
||||
if ($?) {
|
||||
$res++;
|
||||
if ($self->config->no_fail) {
|
||||
print STDERR "gbp pull fails in $_\n";
|
||||
} else {
|
||||
ds_warn "gbp pull failed in $_\n";
|
||||
ds_verbose "Use --no-fail to continue";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
chdir $cdir;
|
||||
} else {
|
||||
spawn(
|
||||
exec => [
|
||||
'gbp', 'clone',
|
||||
'--all', $self->config->git_server_url . $path . ".git"
|
||||
],
|
||||
wait_child => 1,
|
||||
nocheck => 1,
|
||||
);
|
||||
if ($?) {
|
||||
$res++;
|
||||
if ($self->config->no_fail) {
|
||||
print STDERR "gbp clone fails in $_\n";
|
||||
} else {
|
||||
ds_warn "gbp clone failed for $_\n";
|
||||
ds_verbose "Use --no-fail to continue";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
ds_warn "$_ ready in $_/";
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
1;
|
47
lib/Devscripts/Salsa/create_repo.pm
Normal file
47
lib/Devscripts/Salsa/create_repo.pm
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Creates project using name or path
|
||||
package Devscripts::Salsa::create_repo; # create_project
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Dpkg::IPC;
|
||||
use Moo::Role;
|
||||
|
||||
with "Devscripts::Salsa::Hooks";
|
||||
|
||||
sub create_repo {
|
||||
my ($self, $reponame) = @_;
|
||||
unless ($reponame) {
|
||||
ds_warn "Project name is missing";
|
||||
return 1;
|
||||
}
|
||||
# Get parameters from Devscripts::Salsa::Repo
|
||||
my $opts = {
|
||||
name => $reponame,
|
||||
path => $reponame,
|
||||
visibility => 'public',
|
||||
$self->desc($reponame),
|
||||
};
|
||||
if ($self->group_id) {
|
||||
$opts->{namespace_id} = $self->group_id;
|
||||
}
|
||||
return 1
|
||||
if (
|
||||
$ds_yes < 0
|
||||
and ds_prompt(
|
||||
"You're going to create $reponame in "
|
||||
. ($self->group_id ? $self->group_path : 'your namespace')
|
||||
. ". Continue (Y/n) "
|
||||
) =~ refuse
|
||||
);
|
||||
my $repo = eval { $self->api->create_project($opts) };
|
||||
if ($@ or !$repo) {
|
||||
ds_warn "Project not created: $@";
|
||||
return 1;
|
||||
}
|
||||
ds_warn "Project $repo->{web_url} created";
|
||||
$reponame =~ s#^.*/##;
|
||||
$self->add_hooks($repo->{id}, $reponame);
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
26
lib/Devscripts/Salsa/del_repo.pm
Normal file
26
lib/Devscripts/Salsa/del_repo.pm
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Deletes a project
|
||||
package Devscripts::Salsa::del_repo; # delete_project
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Dpkg::IPC;
|
||||
use Moo::Role;
|
||||
|
||||
sub del_repo {
|
||||
my ($self, $reponame) = @_;
|
||||
unless ($reponame) {
|
||||
ds_warn "Project name or path is missing";
|
||||
return 1;
|
||||
}
|
||||
my $id = $self->project2id($reponame) or return 1;
|
||||
my $path = $self->project2path($reponame);
|
||||
return 1
|
||||
if ($ds_yes < 0
|
||||
and ds_prompt("You're going to delete $path. Continue (Y/n) ")
|
||||
=~ refuse);
|
||||
$self->api->delete_project($id);
|
||||
ds_warn "Project $path deleted";
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
32
lib/Devscripts/Salsa/del_user.pm
Normal file
32
lib/Devscripts/Salsa/del_user.pm
Normal file
|
@ -0,0 +1,32 @@
|
|||
# Removes a user from a group
|
||||
package Devscripts::Salsa::del_user; # delete_user
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
sub del_user {
|
||||
my ($self, $user) = @_;
|
||||
unless ($user) {
|
||||
ds_warn "Usage $0 delete_user <user>";
|
||||
return 1;
|
||||
}
|
||||
unless ($self->group_id) {
|
||||
ds_warn "Unable to remove user without --group-id";
|
||||
return 1;
|
||||
}
|
||||
|
||||
my $id = $self->username2id($user) or return 1;
|
||||
return 1
|
||||
if (
|
||||
$ds_yes < 0
|
||||
and ds_prompt(
|
||||
"You're going to remove $user from group $self->{group_id}. Continue (Y/n) "
|
||||
) =~ refuse
|
||||
);
|
||||
$self->api->remove_group_member($self->group_id, $id);
|
||||
ds_warn "User $user removed from group " . $self->group_id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
36
lib/Devscripts/Salsa/fork.pm
Normal file
36
lib/Devscripts/Salsa/fork.pm
Normal file
|
@ -0,0 +1,36 @@
|
|||
# Forks a project given by full path into group/user namespace
|
||||
package Devscripts::Salsa::fork;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Dpkg::IPC;
|
||||
use Moo::Role;
|
||||
|
||||
with 'Devscripts::Salsa::checkout';
|
||||
|
||||
sub fork {
|
||||
my ($self, $project) = @_;
|
||||
unless ($project) {
|
||||
ds_warn "Project to fork is missing";
|
||||
return 1;
|
||||
}
|
||||
my $path = $self->main_path or return 1;
|
||||
$self->api->fork_project($project, { namespace => $path });
|
||||
my $p = $project;
|
||||
$p =~ s#.*/##;
|
||||
if ($self->checkout($p)) {
|
||||
ds_warn "Failed to checkout $project";
|
||||
return 1;
|
||||
}
|
||||
chdir $p;
|
||||
spawn(
|
||||
exec => [
|
||||
qw(git remote add upstream),
|
||||
$self->config->git_server_url . $project
|
||||
],
|
||||
wait_child => 1
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
45
lib/Devscripts/Salsa/forks.pm
Normal file
45
lib/Devscripts/Salsa/forks.pm
Normal file
|
@ -0,0 +1,45 @@
|
|||
# Lists forks of a project
|
||||
package Devscripts::Salsa::forks;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
sub forks {
|
||||
my ($self, @reponames) = @_;
|
||||
my $res = 0;
|
||||
unless (@reponames) {
|
||||
ds_warn "Project name is missing";
|
||||
return 1;
|
||||
}
|
||||
foreach my $p (@reponames) {
|
||||
my $id = $self->project2id($p);
|
||||
unless ($id) {
|
||||
ds_warn "Project $_ not found";
|
||||
$res++;
|
||||
next;
|
||||
}
|
||||
print "$p\n";
|
||||
my $forks = $self->api->paginator(
|
||||
'project_forks',
|
||||
$id,
|
||||
{
|
||||
state => 'opened',
|
||||
});
|
||||
unless ($forks) {
|
||||
print "\n";
|
||||
next;
|
||||
}
|
||||
while ($_ = $forks->next) {
|
||||
print <<END;
|
||||
\tId : $_->{id}
|
||||
\tName: $_->{path_with_namespace}
|
||||
\tURL : $_->{web_url}
|
||||
|
||||
END
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
1;
|
35
lib/Devscripts/Salsa/group.pm
Normal file
35
lib/Devscripts/Salsa/group.pm
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Lists members of a group
|
||||
package Devscripts::Salsa::group; # list_users
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
sub group {
|
||||
my ($self) = @_;
|
||||
my $count = 0;
|
||||
unless ($self->group_id) {
|
||||
ds_warn "Usage $0 --group-id 1234 list_users";
|
||||
return 1;
|
||||
}
|
||||
my $users = $self->api->paginator('group_members', $self->group_id);
|
||||
while ($_ = $users->next) {
|
||||
$count++;
|
||||
my $access_level = $self->levels_code($_->{access_level});
|
||||
print <<END;
|
||||
Id : $_->{id}
|
||||
Username : $_->{username}
|
||||
Name : $_->{name}
|
||||
Access level: $access_level
|
||||
State : $_->{state}
|
||||
|
||||
END
|
||||
}
|
||||
unless ($count) {
|
||||
ds_warn "No users found";
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
20
lib/Devscripts/Salsa/join.pm
Normal file
20
lib/Devscripts/Salsa/join.pm
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Launch request to join a group
|
||||
package Devscripts::Salsa::join;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
sub join {
|
||||
my ($self, $group) = @_;
|
||||
unless ($group ||= $self->config->group || $self->config->group_id) {
|
||||
ds_warn "Group is missing";
|
||||
return 1;
|
||||
}
|
||||
my $gid = $self->group2id($group);
|
||||
$self->api->group_access_requests($gid);
|
||||
ds_warn "Request launched to group $group ($gid)";
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
77
lib/Devscripts/Salsa/last_ci_status.pm
Normal file
77
lib/Devscripts/Salsa/last_ci_status.pm
Normal file
|
@ -0,0 +1,77 @@
|
|||
package Devscripts::Salsa::last_ci_status;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
with "Devscripts::Salsa::Repo";
|
||||
|
||||
use constant OK => 'success';
|
||||
use constant SKIPPED => 'skipped';
|
||||
use constant FAILED => 'failed';
|
||||
|
||||
sub last_ci_status {
|
||||
my ($self, @repos) = @_;
|
||||
unless (@repos or $self->config->all or $self->config->all_archived) {
|
||||
ds_warn "Usage $0 ci_status <--all|--all-archived|names>";
|
||||
return 1;
|
||||
}
|
||||
if (@repos and $self->config->all) {
|
||||
ds_warn "--all with a project name makes no sense";
|
||||
return 1;
|
||||
}
|
||||
if (@repos and $self->config->all_archived) {
|
||||
ds_warn "--all-archived with a project name makes no sense";
|
||||
return 1;
|
||||
}
|
||||
# If --all is asked, launch all projects
|
||||
@repos = map { $_->[1] } $self->get_repo(0, @repos) unless (@repos);
|
||||
my $ret = 0;
|
||||
foreach my $repo (@repos) {
|
||||
my $id = $self->project2id($repo) or return 1;
|
||||
my $pipelines = $self->api->pipelines($id);
|
||||
unless ($pipelines and @$pipelines) {
|
||||
ds_warn "No pipelines for $repo";
|
||||
$ret++;
|
||||
unless ($self->config->no_fail) {
|
||||
ds_verbose "Use --no-fail to continue";
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
my $status = $pipelines->[0]->{status};
|
||||
if ($status eq OK) {
|
||||
print "Last result for $repo: $status\n";
|
||||
} else {
|
||||
print STDERR "Last result for $repo: $status\n";
|
||||
my $jobs
|
||||
= $self->api->pipeline_jobs($id, $pipelines->[0]->{id});
|
||||
my %jres;
|
||||
foreach my $job (sort { $a->{id} <=> $b->{id} } @$jobs) {
|
||||
next if $job->{status} eq SKIPPED;
|
||||
push @{ $jres{ $job->{status} } }, $job->{name};
|
||||
}
|
||||
if ($jres{ OK() }) {
|
||||
print STDERR ' success: '
|
||||
. join(', ', @{ $jres{ OK() } }) . "\n";
|
||||
delete $jres{ OK() };
|
||||
}
|
||||
foreach my $k (sort keys %jres) {
|
||||
print STDERR ' '
|
||||
. uc($k) . ': '
|
||||
. join(', ', @{ $jres{$k} }) . "\n";
|
||||
}
|
||||
print STDERR "\n See: " . $pipelines->[0]->{web_url} . "\n\n";
|
||||
if ($status eq FAILED) {
|
||||
$ret++;
|
||||
unless ($self->config->no_fail) {
|
||||
ds_verbose "Use --no-fail to continue";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
1;
|
40
lib/Devscripts/Salsa/list_groups.pm
Normal file
40
lib/Devscripts/Salsa/list_groups.pm
Normal file
|
@ -0,0 +1,40 @@
|
|||
# Lists subgroups of a group or groups of a user
|
||||
package Devscripts::Salsa::list_groups;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
sub list_groups {
|
||||
my ($self, $match) = @_;
|
||||
my $groups;
|
||||
my $count = 0;
|
||||
my $opts = {
|
||||
order_by => 'name',
|
||||
sort => 'asc',
|
||||
($match ? (search => $match) : ()),
|
||||
};
|
||||
if ($self->group_id) {
|
||||
$groups
|
||||
= $self->api->paginator('group_subgroups', $self->group_id, $opts);
|
||||
} else {
|
||||
$groups = $self->api->paginator('groups', $opts);
|
||||
}
|
||||
while ($_ = $groups->next) {
|
||||
$count++;
|
||||
my $parent = $_->{parent_id} ? "Parent id: $_->{parent_id}\n" : '';
|
||||
print <<END;
|
||||
Id : $_->{id}
|
||||
Name : $_->{name}
|
||||
Full path: $_->{full_path}
|
||||
$parent
|
||||
END
|
||||
}
|
||||
unless ($count) {
|
||||
ds_warn "No groups found";
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
42
lib/Devscripts/Salsa/list_repos.pm
Normal file
42
lib/Devscripts/Salsa/list_repos.pm
Normal file
|
@ -0,0 +1,42 @@
|
|||
# Lists projects of group/user
|
||||
package Devscripts::Salsa::list_repos; # list_projects
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
sub list_repos {
|
||||
my ($self, $match) = @_;
|
||||
my $projects;
|
||||
my $count = 0;
|
||||
my $opts = {
|
||||
order_by => 'name',
|
||||
sort => 'asc',
|
||||
simple => 1,
|
||||
archived => $self->config->archived,
|
||||
($match ? (search => $match) : ()),
|
||||
};
|
||||
if ($self->group_id) {
|
||||
$projects
|
||||
= $self->api->paginator('group_projects', $self->group_id, $opts);
|
||||
} else {
|
||||
$projects
|
||||
= $self->api->paginator('user_projects', $self->user_id, $opts);
|
||||
}
|
||||
while ($_ = $projects->next) {
|
||||
$count++;
|
||||
print <<END;
|
||||
Id : $_->{id}
|
||||
Name: $_->{name}
|
||||
URL : $_->{web_url}
|
||||
|
||||
END
|
||||
}
|
||||
unless ($count) {
|
||||
ds_warn "No projects found";
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
174
lib/Devscripts/Salsa/merge_request.pm
Normal file
174
lib/Devscripts/Salsa/merge_request.pm
Normal file
|
@ -0,0 +1,174 @@
|
|||
# Creates a merge request from current directory (or using parameters)
|
||||
package Devscripts::Salsa::merge_request;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Dpkg::IPC;
|
||||
use Moo::Role;
|
||||
|
||||
with 'Devscripts::Salsa::search_project'; # search_projects
|
||||
|
||||
sub merge_request {
|
||||
my ($self, $dst_project, $dst_branch) = @_;
|
||||
my $src_branch = $self->config->mr_src_branch;
|
||||
my $src_project = $self->config->mr_src_project;
|
||||
$dst_project ||= $self->config->mr_dst_project;
|
||||
$dst_branch ||= $self->config->mr_dst_branch;
|
||||
my $title = $self->config->mr_title;
|
||||
my $desc = $self->config->mr_desc;
|
||||
|
||||
if ($src_branch) {
|
||||
unless ($src_project and $dst_project) {
|
||||
ds_warn "--mr-src-project and --mr-src-project "
|
||||
. "are required when --mr-src-branch is set";
|
||||
return 1;
|
||||
}
|
||||
unless ($src_project =~ m#/#) {
|
||||
$src_project = $self->project2path($src_project);
|
||||
}
|
||||
} else { # Use current repository to find elements
|
||||
ds_verbose "using current branch as source";
|
||||
my $out;
|
||||
unless ($src_project) {
|
||||
# 1. Verify that project is ready
|
||||
spawn(
|
||||
exec => [qw(git status -s -b -uno)],
|
||||
wait_child => 1,
|
||||
to_string => \$out
|
||||
);
|
||||
chomp $out;
|
||||
# Case "rebased"
|
||||
if ($out =~ /\[/) {
|
||||
ds_warn "Current branch isn't pushed, aborting:\n";
|
||||
return 1;
|
||||
}
|
||||
# Case else: nothing after src...dst
|
||||
unless ($out =~ /\s(\S+)\.\.\.(\S+)/s) {
|
||||
ds_warn
|
||||
"Current branch has no origin or isn't pushed, aborting";
|
||||
return 1;
|
||||
}
|
||||
# 2. Set source branch to current branch
|
||||
$src_branch ||= $1;
|
||||
ds_verbose "Found current branch: $src_branch";
|
||||
}
|
||||
unless ($src_project and $dst_project) {
|
||||
# Check remote links
|
||||
spawn(
|
||||
exec => [qw(git remote --verbose show)],
|
||||
wait_child => 1,
|
||||
to_string => \$out,
|
||||
);
|
||||
my $origin = $self->config->api_url;
|
||||
$origin =~ s#api/v4$##;
|
||||
# 3. Set source project using "origin" target
|
||||
unless ($src_project) {
|
||||
if ($out
|
||||
=~ /origin\s+(?:\Q$self->{config}->{git_server_url}\E|\Q$origin\E)(\S*)/m
|
||||
) {
|
||||
$src_project = $1;
|
||||
$src_project =~ s/\.git$//;
|
||||
} else {
|
||||
ds_warn
|
||||
"Unable to find project origin, set it using --mr-src-project";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
# 4. Steps to find destination project:
|
||||
# - command-line
|
||||
# - GitLab API (search for "forked_from_project"
|
||||
# - "upstream" in git remote
|
||||
# - use source project as destination project
|
||||
|
||||
# 4.1. Stop if dest project has been given in command line
|
||||
unless ($dst_project) {
|
||||
my $project = $self->api->project($src_project);
|
||||
|
||||
# 4.2. Search original project from GitLab API
|
||||
if ($project->{forked_from_project}) {
|
||||
$dst_project
|
||||
= $project->{forked_from_project}->{path_with_namespace};
|
||||
}
|
||||
if ($dst_project) {
|
||||
ds_verbose "Project was forked from $dst_project";
|
||||
|
||||
# 4.3. Search for an "upstream" target in `git remote`
|
||||
} elsif ($out
|
||||
=~ /upstream\s+(?:\Q$self->{config}->{git_server_url}\E|\Q$origin\E)(\S*)/m
|
||||
) {
|
||||
$dst_project = $1;
|
||||
$dst_project =~ s/\.git$//;
|
||||
ds_verbose 'Use "upstream" target as dst project';
|
||||
# 4.4. Use source project as destination
|
||||
} else {
|
||||
ds_warn
|
||||
"No upstream target found, using current project as target";
|
||||
$dst_project = $src_project;
|
||||
}
|
||||
ds_verbose "Use $dst_project as dest project";
|
||||
}
|
||||
}
|
||||
# 5. Search for MR title and desc
|
||||
unless ($title) {
|
||||
ds_warn "Title not set, using last commit";
|
||||
spawn(
|
||||
exec => ['git', 'show', '--format=format:%s###%b'],
|
||||
wait_child => 1,
|
||||
to_string => \$out,
|
||||
);
|
||||
$out =~ s/\ndiff.*$//s;
|
||||
my ($t, $d) = split /###/, $out;
|
||||
chomp $d;
|
||||
$title = $t;
|
||||
ds_verbose "Title set to $title";
|
||||
$desc ||= $d;
|
||||
# Replace all bug links by markdown links
|
||||
if ($desc) {
|
||||
$desc =~ s@#(\d{6,})\b@[#$1](https://bugs.debian.org/$1)@mg;
|
||||
ds_verbose "Desc set to $desc";
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($dst_project eq 'same') {
|
||||
$dst_project = $src_project;
|
||||
}
|
||||
my $src = $self->api->project($src_project);
|
||||
unless ($title) {
|
||||
ds_warn "Title is required";
|
||||
return 1;
|
||||
}
|
||||
unless ($src and $src->{id}) {
|
||||
ds_warn "Target project not found $src_project";
|
||||
return 1;
|
||||
}
|
||||
my $dst;
|
||||
if ($dst_project) {
|
||||
$dst = $self->api->project($dst_project);
|
||||
unless ($dst and $dst->{id}) {
|
||||
ds_warn "Target project not found";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 1
|
||||
if (
|
||||
ds_prompt(
|
||||
"You're going to push an MR to $dst_project:$dst_branch. Continue (Y/n)"
|
||||
) =~ refuse
|
||||
);
|
||||
my $res = $self->api->create_merge_request(
|
||||
$src->{id},
|
||||
{
|
||||
source_branch => $src_branch,
|
||||
target_branch => $dst_branch,
|
||||
title => $title,
|
||||
remove_source_branch => $self->config->mr_remove_source_branch,
|
||||
squash => $self->config->mr_allow_squash,
|
||||
($dst ? (target_project_id => $dst->{id}) : ()),
|
||||
($desc ? (description => $desc) : ()),
|
||||
});
|
||||
ds_warn "MR '$title' posted:";
|
||||
ds_warn $res->{web_url};
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
49
lib/Devscripts/Salsa/merge_requests.pm
Normal file
49
lib/Devscripts/Salsa/merge_requests.pm
Normal file
|
@ -0,0 +1,49 @@
|
|||
# Lists merge requests proposed to a project
|
||||
package Devscripts::Salsa::merge_requests;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
sub merge_requests {
|
||||
my ($self, @reponames) = @_;
|
||||
my $res = 1;
|
||||
unless (@reponames) {
|
||||
ds_warn "project name is missing";
|
||||
return 1;
|
||||
}
|
||||
foreach my $p (@reponames) {
|
||||
my $id = $self->project2id($p);
|
||||
my $count = 0;
|
||||
unless ($id) {
|
||||
ds_warn "Project $_ not found";
|
||||
return 1;
|
||||
}
|
||||
print "$p\n";
|
||||
my $mrs = $self->api->paginator(
|
||||
'merge_requests',
|
||||
$id,
|
||||
{
|
||||
state => 'opened',
|
||||
});
|
||||
while ($_ = $mrs->next) {
|
||||
$res = 0;
|
||||
my $status = $_->{work_in_progress} ? 'WIP' : $_->{merge_status};
|
||||
print <<END;
|
||||
\tId : $_->{id}
|
||||
\tTitle : $_->{title}
|
||||
\tAuthor: $_->{author}->{username}
|
||||
\tStatus: $status
|
||||
\tUrl : $_->{web_url}
|
||||
|
||||
END
|
||||
}
|
||||
unless ($count) {
|
||||
print "\n";
|
||||
next;
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
1;
|
127
lib/Devscripts/Salsa/pipeline_schedule.pm
Executable file
127
lib/Devscripts/Salsa/pipeline_schedule.pm
Executable file
|
@ -0,0 +1,127 @@
|
|||
# Create a pipeline schedule using parameters
|
||||
package Devscripts::Salsa::pipeline_schedule;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
# For --all
|
||||
with "Devscripts::Salsa::Repo";
|
||||
|
||||
sub pipeline_schedule {
|
||||
my ($self, @repos) = @_;
|
||||
my $ret = 0;
|
||||
my $desc = $self->config->schedule_desc;
|
||||
my $ref = $self->config->schedule_ref;
|
||||
my $cron = $self->config->schedule_cron;
|
||||
my $tz = $self->config->schedule_tz;
|
||||
my $active = $self->config->schedule_enable;
|
||||
$active
|
||||
= ($self->config->schedule_disable)
|
||||
? "0"
|
||||
: $active;
|
||||
my $run = $self->config->schedule_run;
|
||||
my $delete = $self->config->schedule_delete;
|
||||
|
||||
unless (@repos or $self->config->all) {
|
||||
ds_warn "Usage $0 pipeline <project|--all>";
|
||||
return 1;
|
||||
}
|
||||
if (@repos and $self->config->all) {
|
||||
ds_warn "--all with a project (@repos) makes no sense";
|
||||
return 1;
|
||||
}
|
||||
|
||||
unless ($desc) {
|
||||
ds_warn "--schedule-desc / SALSA_SCHEDULE_DESC is missing";
|
||||
ds_warn "Are you looking for: $0 pipelines <project|--all>";
|
||||
return 1;
|
||||
}
|
||||
|
||||
# If --all is asked, launch all projects
|
||||
@repos = map { $_->[1] } $self->get_repo(0, @repos) unless (@repos);
|
||||
|
||||
foreach my $repo (sort @repos) {
|
||||
my $id = $self->project2id($repo);
|
||||
unless ($id) {
|
||||
#ds_warn "Project $repo not found"; # $self->project2id($repo) shows this error
|
||||
$ret++;
|
||||
unless ($self->config->no_fail) {
|
||||
ds_verbose "Use --no-fail to continue";
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
my @pipe_id = ();
|
||||
$desc =~ s/%p/$repo/g;
|
||||
my $options = {};
|
||||
$options->{ref} = $ref if defined $ref;
|
||||
$options->{cron} = $cron if defined $cron;
|
||||
$options->{cron_timezone} = $tz if defined $tz;
|
||||
$options->{active} = $active if defined $active;
|
||||
|
||||
# REF: https://docs.gitlab.com/ee/api/pipeline_schedules.html#get-all-pipeline-schedules
|
||||
# $self->api->pipeline_schedules($id)
|
||||
my $pipelines
|
||||
= $self->api->paginator('pipeline_schedules', $id)->all();
|
||||
ds_verbose "No pipelines scheduled for $repo" unless @$pipelines;
|
||||
|
||||
foreach (@$pipelines) {
|
||||
push @pipe_id, $_->{id}
|
||||
if ($_->{description} eq $desc);
|
||||
}
|
||||
|
||||
ds_warn "More than 1 scheduled pipeline matches: $desc ("
|
||||
. ++$#pipe_id . ")"
|
||||
if ($pipe_id[1]);
|
||||
|
||||
if (!@pipe_id) {
|
||||
ds_warn "--schedule-ref / SALSA_SCHEDULE_REF is required"
|
||||
unless ($ref);
|
||||
ds_warn "--schedule-cron / SALSA_SCHEDULE_CRON is required"
|
||||
unless ($cron);
|
||||
return 1
|
||||
unless ($ref && $cron);
|
||||
|
||||
$options->{description} = $desc if defined $desc;
|
||||
|
||||
ds_verbose "No scheduled pipelines matching: $desc. Creating!";
|
||||
my $schedule
|
||||
= $self->api->create_pipeline_schedule($id, $options);
|
||||
|
||||
@pipe_id = $schedule->{id};
|
||||
} elsif (keys %$options) {
|
||||
ds_verbose "Editing scheduled pipelines matching: $desc";
|
||||
foreach (@pipe_id) {
|
||||
next if !$_;
|
||||
|
||||
my $schedule
|
||||
= $self->api->edit_pipeline_schedule($id, $_, $options);
|
||||
}
|
||||
}
|
||||
|
||||
if ($run) {
|
||||
ds_verbose "Running scheduled pipelines matching: $desc";
|
||||
|
||||
foreach (@pipe_id) {
|
||||
next if !$_;
|
||||
|
||||
my $schedule = $self->api->run_pipeline_schedule($id, $_);
|
||||
}
|
||||
}
|
||||
|
||||
if ($delete) {
|
||||
ds_verbose "Deleting scheduled pipelines matching: $desc";
|
||||
|
||||
foreach (@pipe_id) {
|
||||
next if !$_;
|
||||
|
||||
my $schedule
|
||||
= $self->api->delete_pipeline_schedule($id, $_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
1;
|
73
lib/Devscripts/Salsa/pipeline_schedules.pm
Executable file
73
lib/Devscripts/Salsa/pipeline_schedules.pm
Executable file
|
@ -0,0 +1,73 @@
|
|||
# Lists pipeline schedules of a project
|
||||
package Devscripts::Salsa::pipeline_schedules;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
# For --all
|
||||
with "Devscripts::Salsa::Repo";
|
||||
|
||||
sub pipeline_schedules {
|
||||
my ($self, @repo) = @_;
|
||||
my $ret = 0;
|
||||
|
||||
unless (@repo or $self->config->all) {
|
||||
ds_warn "Usage $0 pipelines <project|--all>";
|
||||
return 1;
|
||||
}
|
||||
if (@repo and $self->config->all) {
|
||||
ds_warn "--all with a project (@repo) makes no sense";
|
||||
return 1;
|
||||
}
|
||||
|
||||
# If --all is asked, launch all projects
|
||||
@repo = map { $_->[1] } $self->get_repo(0, @repo) unless (@repo);
|
||||
|
||||
foreach my $p (sort @repo) {
|
||||
my $id = $self->project2id($p);
|
||||
my $count = 0;
|
||||
unless ($id) {
|
||||
#ds_warn "Project $p not found"; # $self->project2id($p) shows this error
|
||||
$ret++;
|
||||
unless ($self->config->no_fail) {
|
||||
ds_verbose "Use --no-fail to continue";
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
my $projects = $self->api->project($id);
|
||||
if ($projects->{jobs_enabled} == 0) {
|
||||
print "$p has disabled CI/CD\n";
|
||||
next;
|
||||
}
|
||||
|
||||
my $pipelines
|
||||
= $self->api->paginator('pipeline_schedules', $id)->all();
|
||||
|
||||
print "$p\n" if @$pipelines;
|
||||
|
||||
foreach (@$pipelines) {
|
||||
my $status = $_->{active} ? 'Enabled' : 'Disabled';
|
||||
print <<END;
|
||||
\tID : $_->{id}
|
||||
\tDescription: $_->{description}
|
||||
\tStatus : $status
|
||||
\tRef : $_->{ref}
|
||||
\tCron : $_->{cron}
|
||||
\tTimezone : $_->{cron_timezone}
|
||||
\tCreated : $_->{created_at}
|
||||
\tUpdated : $_->{updated_at}
|
||||
\tNext run : $_->{next_run_at}
|
||||
\tOwner : $_->{owner}->{username}
|
||||
|
||||
END
|
||||
}
|
||||
}
|
||||
unless ($count) {
|
||||
next;
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
1;
|
43
lib/Devscripts/Salsa/protect_branch.pm
Normal file
43
lib/Devscripts/Salsa/protect_branch.pm
Normal file
|
@ -0,0 +1,43 @@
|
|||
# Protects a branch
|
||||
package Devscripts::Salsa::protect_branch;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
use constant levels => {
|
||||
o => 50,
|
||||
owner => 50,
|
||||
m => 40,
|
||||
maintainer => 40,
|
||||
d => 30,
|
||||
developer => 30,
|
||||
r => 20,
|
||||
reporter => 20,
|
||||
g => 10,
|
||||
guest => 10,
|
||||
};
|
||||
|
||||
sub protect_branch {
|
||||
my ($self, $reponame, $branch, $merge, $push) = @_;
|
||||
unless ($reponame and $branch) {
|
||||
ds_warn "usage: $0 protect_branch project branch merge push";
|
||||
return 1;
|
||||
}
|
||||
if (defined $merge and $merge =~ /^(?:no|0)$/i) {
|
||||
$self->api->unprotect_branch($self->project2id($reponame), $branch);
|
||||
return 0;
|
||||
}
|
||||
unless (levels->{$merge} and levels->{$push}) {
|
||||
ds_warn
|
||||
"usage: $0 protect_branch project branch <merge level> <push level>";
|
||||
return 1;
|
||||
}
|
||||
my $opts = { name => $branch };
|
||||
$opts->{push_access_level} = (levels->{$push});
|
||||
$opts->{merge_access_level} = (levels->{$merge});
|
||||
$self->api->protect_branch($self->project2id($reponame), $opts);
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
27
lib/Devscripts/Salsa/protected_branches.pm
Normal file
27
lib/Devscripts/Salsa/protected_branches.pm
Normal file
|
@ -0,0 +1,27 @@
|
|||
# Displays protected branches of a project
|
||||
package Devscripts::Salsa::protected_branches;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
sub protected_branches {
|
||||
my ($self, $reponame) = @_;
|
||||
unless ($reponame) {
|
||||
ds_warn "Project name is missing";
|
||||
return 1;
|
||||
}
|
||||
my $branches
|
||||
= $self->api->protected_branches($self->project2id($reponame));
|
||||
if ($branches and @$branches) {
|
||||
printf " %-20s | %-25s | %-25s\n", 'Branch', 'Merge', 'Push';
|
||||
foreach (@$branches) {
|
||||
printf " %-20s | %-25s | %-25s\n", $_->{name},
|
||||
$_->{merge_access_levels}->[0]->{access_level_description},
|
||||
$_->{push_access_levels}->[0]->{access_level_description};
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
15
lib/Devscripts/Salsa/purge_cache.pm
Normal file
15
lib/Devscripts/Salsa/purge_cache.pm
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Empties the Devscripts::JSONCache
|
||||
package Devscripts::Salsa::purge_cache;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
sub purge_cache {
|
||||
my @keys = keys %{ $_[0]->_cache };
|
||||
delete $_[0]->_cache->{$_} foreach (@keys);
|
||||
ds_verbose "Cache empty";
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
106
lib/Devscripts/Salsa/push.pm
Normal file
106
lib/Devscripts/Salsa/push.pm
Normal file
|
@ -0,0 +1,106 @@
|
|||
# Push local work. Like gbp push but able to push incomplete work
|
||||
package Devscripts::Salsa::push;
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Devscripts::Utils;
|
||||
use Dpkg::Source::Format;
|
||||
use Moo::Role;
|
||||
use Dpkg::IPC;
|
||||
|
||||
sub readGbpConf {
|
||||
my ($self) = @_;
|
||||
my $res = '';
|
||||
foreach my $gbpconf (qw(.gbp.conf debian/gbp.conf .git/gbp.conf)) {
|
||||
if (-e $gbpconf) {
|
||||
open(my $f, $gbpconf);
|
||||
while (<$f>) {
|
||||
$res .= $_;
|
||||
if (/^\s*(debian|upstream)\-(branch|tag)\s*=\s*(.*\S)/) {
|
||||
$self->{"$1_$2"} = $3;
|
||||
}
|
||||
}
|
||||
close $f;
|
||||
last;
|
||||
}
|
||||
}
|
||||
if ($self->{debian_tag}) {
|
||||
$self->{debian_tag} =~ s/%\(version\)s/.*/g;
|
||||
$self->{debian_tag} =~ s/^/^/;
|
||||
$self->{debian_tag} =~ s/$/\$/;
|
||||
} else {
|
||||
my @tmp
|
||||
= Dpkg::Source::Format->new(filename => 'debian/source/format')->get;
|
||||
$self->{debian_tag} = $tmp[2] eq 'native' ? '.*' : '^debian/.*$';
|
||||
}
|
||||
if ($self->{upstream_tag}) {
|
||||
$self->{upstream_tag} =~ s/%\(version\)s/.*/g;
|
||||
$self->{upstream_tag} =~ s/^/^/;
|
||||
$self->{upstream_tag} =~ s/$/\$/;
|
||||
} else {
|
||||
$self->{upstream_tag} = '^upstream/.*$';
|
||||
}
|
||||
$self->{debian_branch} ||= 'master';
|
||||
$self->{upstream_branch} ||= 'upstream';
|
||||
return $res;
|
||||
}
|
||||
|
||||
sub push {
|
||||
my ($self) = @_;
|
||||
$self->readGbpConf;
|
||||
my @refs;
|
||||
foreach (
|
||||
$self->{debian_branch}, $self->{upstream_branch},
|
||||
'pristine-tar', 'refs/notes/commits'
|
||||
) {
|
||||
if (ds_exec_no_fail(qw(git rev-parse --verify --quiet), $_) == 0) {
|
||||
push @refs, $_;
|
||||
}
|
||||
}
|
||||
my $out;
|
||||
spawn(exec => ['git', 'tag'], wait_child => 1, to_string => \$out);
|
||||
my @tags = grep /(?:$self->{debian_tag}|$self->{upstream_tag})/,
|
||||
split(/\r?\n/, $out);
|
||||
unless (
|
||||
$ds_yes < 0
|
||||
and ds_prompt(
|
||||
"You're going to push :\n - "
|
||||
. join(', ', @refs)
|
||||
. "\nand check tags that match:\n - "
|
||||
. join(', ', $self->{debian_tag}, $self->{upstream_tag})
|
||||
. "\nContinue (Y/n) "
|
||||
) =~ refuse
|
||||
) {
|
||||
my $origin;
|
||||
eval {
|
||||
spawn(
|
||||
exec => ['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
|
||||
wait_child => 1,
|
||||
to_string => \$out,
|
||||
);
|
||||
chomp $out;
|
||||
spawn(
|
||||
exec =>
|
||||
['git', 'config', '--local', '--get', "branch.$out.remote"],
|
||||
wait_child => 1,
|
||||
to_string => \$origin,
|
||||
);
|
||||
chomp $origin;
|
||||
};
|
||||
if ($origin) {
|
||||
ds_verbose 'Origin is ' . $origin;
|
||||
} else {
|
||||
ds_warn 'Unable to detect remote name, trying "origin"';
|
||||
ds_verbose "Error: $@" if ($@);
|
||||
$origin = 'origin';
|
||||
}
|
||||
ds_verbose "Execute 'git push $origin " . join(' ', @refs, '<tags>');
|
||||
ds_debug "Tags are: " . join(' ', @tags);
|
||||
spawn(
|
||||
exec => ['git', 'push', $origin, @refs, @tags],
|
||||
wait_child => 1
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
71
lib/Devscripts/Salsa/push_repo.pm
Normal file
71
lib/Devscripts/Salsa/push_repo.pm
Normal file
|
@ -0,0 +1,71 @@
|
|||
# Creates GitLab project from local repository path
|
||||
package Devscripts::Salsa::push_repo;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Dpkg::IPC;
|
||||
use Moo::Role;
|
||||
|
||||
with "Devscripts::Salsa::create_repo"; # create_project
|
||||
|
||||
sub push_repo {
|
||||
my ($self, $reponame) = @_;
|
||||
unless ($reponame) {
|
||||
ds_warn "Repository path is missing";
|
||||
return 1;
|
||||
}
|
||||
unless (-d $reponame) {
|
||||
ds_warn "$reponame isn't a directory";
|
||||
return 1;
|
||||
}
|
||||
chdir $reponame;
|
||||
eval {
|
||||
spawn(
|
||||
exec => ['dpkg-parsechangelog', '--show-field', 'Source'],
|
||||
to_string => \$reponame,
|
||||
wait_child => 1,
|
||||
);
|
||||
};
|
||||
if ($@) {
|
||||
ds_warn $@;
|
||||
return 1;
|
||||
}
|
||||
chomp $reponame;
|
||||
my $out;
|
||||
spawn(
|
||||
exec => ['git', 'remote', 'show'],
|
||||
to_string => \$out,
|
||||
wait_child => 1,
|
||||
);
|
||||
if ($out =~ /^origin$/m) {
|
||||
ds_warn "git origin is already configured:\n$out";
|
||||
return 1;
|
||||
}
|
||||
my $path = $self->project2path('') or return 1;
|
||||
my $url = $self->config->git_server_url . "$path$reponame";
|
||||
spawn(
|
||||
exec => ['git', 'remote', 'add', 'origin', $url],
|
||||
wait_child => 1,
|
||||
);
|
||||
my $res = $self->create_repo($reponame);
|
||||
if ($res) {
|
||||
return 1
|
||||
unless (
|
||||
ds_prompt(
|
||||
"Project already exists, do you want to try to push local repository? (y/N) "
|
||||
) =~ accept
|
||||
);
|
||||
}
|
||||
spawn(
|
||||
exec =>
|
||||
['git', 'push', '--all', '--verbose', '--set-upstream', 'origin'],
|
||||
wait_child => 1,
|
||||
);
|
||||
spawn(
|
||||
exec => ['git', 'push', '--tags', '--verbose', 'origin'],
|
||||
wait_child => 1,
|
||||
);
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
47
lib/Devscripts/Salsa/rename_branch.pm
Normal file
47
lib/Devscripts/Salsa/rename_branch.pm
Normal file
|
@ -0,0 +1,47 @@
|
|||
package Devscripts::Salsa::rename_branch;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
with "Devscripts::Salsa::Repo";
|
||||
|
||||
our $prompt = 1;
|
||||
|
||||
sub rename_branch {
|
||||
my ($self, @reponames) = @_;
|
||||
my $res = 0;
|
||||
my @repos = $self->get_repo($prompt, @reponames);
|
||||
return @repos unless (ref $repos[0]); # get_repo returns 1 when fails
|
||||
foreach (@repos) {
|
||||
my $id = $_->[0];
|
||||
my $str = $_->[1];
|
||||
if (!$id) {
|
||||
ds_warn "Branch rename has failed for $str (missing ID)\n";
|
||||
return 1;
|
||||
}
|
||||
ds_verbose "Configuring $str";
|
||||
my $project = $self->api->project($id);
|
||||
eval {
|
||||
$self->api->create_branch(
|
||||
$id,
|
||||
{
|
||||
ref => $self->config->source_branch,
|
||||
branch => $self->config->dest_branch,
|
||||
});
|
||||
$self->api->delete_branch($id, $self->config->source_branch);
|
||||
};
|
||||
if ($@) {
|
||||
ds_warn "Branch rename has failed for $str\n";
|
||||
ds_verbose $@;
|
||||
unless ($self->config->no_fail) {
|
||||
ds_verbose "Use --no-fail to continue";
|
||||
return 1;
|
||||
}
|
||||
next;
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
1;
|
37
lib/Devscripts/Salsa/search_group.pm
Normal file
37
lib/Devscripts/Salsa/search_group.pm
Normal file
|
@ -0,0 +1,37 @@
|
|||
# Searches groups using given string
|
||||
package Devscripts::Salsa::search_group; # search_groups
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
sub search_group {
|
||||
my ($self, $group) = @_;
|
||||
unless ($group) {
|
||||
ds_warn "Searched string is missing";
|
||||
return 1;
|
||||
}
|
||||
my $groups = $self->api->group_without_projects($group);
|
||||
if ($groups) {
|
||||
$groups = [$groups];
|
||||
} else {
|
||||
$groups = $self->api->paginator('groups',
|
||||
{ search => $group, order_by => 'name' })->all;
|
||||
}
|
||||
unless ($groups and @$groups) {
|
||||
ds_warn "No group found";
|
||||
return 1;
|
||||
}
|
||||
foreach (@$groups) {
|
||||
print <<END;
|
||||
Id : $_->{id}
|
||||
Name : $_->{name}
|
||||
Full name: $_->{full_name}
|
||||
Full path: $_->{full_path}
|
||||
|
||||
END
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
57
lib/Devscripts/Salsa/search_project.pm
Normal file
57
lib/Devscripts/Salsa/search_project.pm
Normal file
|
@ -0,0 +1,57 @@
|
|||
# Searches projects using given string
|
||||
package Devscripts::Salsa::search_project; # search_projects
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
sub search_project {
|
||||
my ($self, $project) = @_;
|
||||
unless ($project) {
|
||||
ds_warn "Searched string is missing";
|
||||
return 1;
|
||||
}
|
||||
my $projects = $self->api->project($project);
|
||||
if ($projects) {
|
||||
$projects = [$projects];
|
||||
} else {
|
||||
$projects = $self->api->paginator(
|
||||
'projects',
|
||||
{
|
||||
search => $project,
|
||||
order_by => 'name',
|
||||
archived => $self->config->archived
|
||||
})->all();
|
||||
}
|
||||
unless ($projects and @$projects) {
|
||||
ds_warn "No projects found";
|
||||
return 1;
|
||||
}
|
||||
foreach (@$projects) {
|
||||
print <<END;
|
||||
Id : $_->{id}
|
||||
Name : $_->{name}
|
||||
Full path: $_->{path_with_namespace}
|
||||
END
|
||||
print(
|
||||
$_->{namespace}->{kind} eq 'group'
|
||||
? "Group id : "
|
||||
: "User id : "
|
||||
);
|
||||
print "$_->{namespace}->{id}\n";
|
||||
print(
|
||||
$_->{namespace}->{kind} eq 'group'
|
||||
? "Group : "
|
||||
: "User : "
|
||||
);
|
||||
print "$_->{namespace}->{name}\n";
|
||||
if ($_->{forked_from_project} and $_->{forked_from_project}->{id}) {
|
||||
print
|
||||
"Fork of : $_->{forked_from_project}->{name_with_namespace}\n";
|
||||
}
|
||||
print "\n";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
36
lib/Devscripts/Salsa/search_user.pm
Normal file
36
lib/Devscripts/Salsa/search_user.pm
Normal file
|
@ -0,0 +1,36 @@
|
|||
# Searches users using given string
|
||||
package Devscripts::Salsa::search_user; # search_users
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
sub search_user {
|
||||
my ($self, $user) = @_;
|
||||
unless ($user) {
|
||||
ds_warn "User name is missing";
|
||||
return 1;
|
||||
}
|
||||
my $users = $self->api->user($user);
|
||||
if ($users) {
|
||||
$users = [$users];
|
||||
} else {
|
||||
$users = $self->api->paginator('users', { search => $user })->all();
|
||||
}
|
||||
unless ($users and @$users) {
|
||||
ds_warn "No user found";
|
||||
return 1;
|
||||
}
|
||||
foreach (@$users) {
|
||||
print <<END;
|
||||
Id : $_->{id}
|
||||
Username : $_->{username}
|
||||
Name : $_->{name}
|
||||
State : $_->{state}
|
||||
|
||||
END
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
137
lib/Devscripts/Salsa/update_repo.pm
Executable file
137
lib/Devscripts/Salsa/update_repo.pm
Executable file
|
@ -0,0 +1,137 @@
|
|||
# Updates projects
|
||||
package Devscripts::Salsa::update_repo; # update_projects
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use GitLab::API::v4::Constants qw(:all);
|
||||
use Moo::Role;
|
||||
|
||||
with "Devscripts::Salsa::Repo";
|
||||
|
||||
our $prompt = 1;
|
||||
|
||||
sub update_repo {
|
||||
my ($self, @reponames) = @_;
|
||||
if ($ds_yes < 0 and $self->config->command eq 'update_repo') {
|
||||
ds_warn
|
||||
"update_projects can't be launched when --info is set, use update_safe";
|
||||
return 1;
|
||||
}
|
||||
unless (@reponames or $self->config->all or $self->config->all_archived) {
|
||||
ds_warn "Usage $0 update_projects <--all|--all-archived|names>";
|
||||
return 1;
|
||||
}
|
||||
if (@reponames and $self->config->all) {
|
||||
ds_warn "--all with a project name makes no sense";
|
||||
return 1;
|
||||
}
|
||||
if (@reponames and $self->config->all_archived) {
|
||||
ds_warn "--all-archived with a project name makes no sense";
|
||||
return 1;
|
||||
}
|
||||
return $self->_update_repo(@reponames);
|
||||
}
|
||||
|
||||
sub _update_repo {
|
||||
my ($self, @reponames) = @_;
|
||||
my $res = 0;
|
||||
# Common options
|
||||
my $configparams = {};
|
||||
# visibility can be modified only by group owners
|
||||
$configparams->{visibility} = 'public'
|
||||
if $self->access_level >= $GITLAB_ACCESS_LEVEL_OWNER;
|
||||
# get project list using Devscripts::Salsa::Repo
|
||||
my @repos = $self->get_repo($prompt, @reponames);
|
||||
return @repos unless (ref $repos[0]); # get_repo returns 1 when fails
|
||||
foreach my $repo (@repos) {
|
||||
my $id = $repo->[0];
|
||||
my $str = $repo->[1];
|
||||
ds_verbose "Configuring $str";
|
||||
eval {
|
||||
# apply new parameters
|
||||
$self->api->edit_project($id,
|
||||
{ %$configparams, $self->desc($str) });
|
||||
# Set project avatar
|
||||
my @avatar_file = $self->desc_multipart($str);
|
||||
$self->api->edit_project_multipart($id, {@avatar_file})
|
||||
if (@avatar_file and $self->config->avatar_path);
|
||||
# add hooks if needed
|
||||
$str =~ s#^.*/##;
|
||||
$self->add_hooks($id, $str);
|
||||
};
|
||||
if ($@) {
|
||||
ds_warn "update_projects has failed for $str\n";
|
||||
ds_verbose $@;
|
||||
$res++;
|
||||
unless ($self->config->no_fail) {
|
||||
ds_verbose "Use --no-fail to continue";
|
||||
return 1;
|
||||
}
|
||||
next;
|
||||
} elsif ($self->config->rename_head) {
|
||||
# 1 - creates new branch if --rename-head
|
||||
my $project = $self->api->project($id);
|
||||
if ($project->{default_branch} ne $self->config->dest_branch) {
|
||||
eval {
|
||||
$self->api->create_branch(
|
||||
$id,
|
||||
{
|
||||
ref => $self->config->source_branch,
|
||||
branch => $self->config->dest_branch,
|
||||
});
|
||||
};
|
||||
if ($@) {
|
||||
ds_debug $@ if ($@);
|
||||
$project = undef;
|
||||
}
|
||||
|
||||
eval {
|
||||
$self->api->edit_project($id,
|
||||
{ default_branch => $self->config->dest_branch });
|
||||
# delete old branch only if "create_branch" succeed
|
||||
if ($project) {
|
||||
$self->api->delete_branch($id,
|
||||
$self->config->source_branch);
|
||||
}
|
||||
};
|
||||
if ($@) {
|
||||
ds_warn "Branch rename has failed for $str\n";
|
||||
ds_verbose $@;
|
||||
$res++;
|
||||
unless ($self->config->no_fail) {
|
||||
ds_verbose "Use --no-fail to continue";
|
||||
return 1;
|
||||
}
|
||||
next;
|
||||
}
|
||||
} else {
|
||||
ds_verbose "Head already renamed for $str";
|
||||
}
|
||||
}
|
||||
ds_verbose "Project $str updated";
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
sub access_level {
|
||||
my ($self) = @_;
|
||||
my $user_id = $self->api->current_user()->{id};
|
||||
if ($self->group_id) {
|
||||
my $tmp = $self->api->all_group_members($self->group_id,
|
||||
{ user_ids => $user_id });
|
||||
unless ($tmp) {
|
||||
my $members
|
||||
= $self->api->paginator('all_group_members', $self->group_id,
|
||||
{ query => $user_id });
|
||||
while ($_ = $members->next) {
|
||||
return $_->{access_level} if ($_->{id} eq $user_id);
|
||||
}
|
||||
ds_warn "You're not member of this group";
|
||||
return 0;
|
||||
}
|
||||
return $tmp->[0]->{access_level};
|
||||
}
|
||||
return $GITLAB_ACCESS_LEVEL_OWNER;
|
||||
}
|
||||
|
||||
1;
|
22
lib/Devscripts/Salsa/update_safe.pm
Normal file
22
lib/Devscripts/Salsa/update_safe.pm
Normal file
|
@ -0,0 +1,22 @@
|
|||
# launches check_projects and launch update_projects if user agrees with this changes
|
||||
package Devscripts::Salsa::update_safe;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
with 'Devscripts::Salsa::check_repo'; # check_projects
|
||||
with 'Devscripts::Salsa::update_repo'; # update_projects
|
||||
|
||||
sub update_safe {
|
||||
my $self = shift;
|
||||
my ($res, $fails) = $self->_check_repo(@_);
|
||||
return 0 unless ($res);
|
||||
return $res
|
||||
if (ds_prompt("$res projects misconfigured, update them ? (Y/n) ")
|
||||
=~ refuse);
|
||||
$Devscripts::Salsa::update_repo::prompt = 0;
|
||||
return $self->_update_repo(@$fails);
|
||||
}
|
||||
|
||||
1;
|
38
lib/Devscripts/Salsa/update_user.pm
Normal file
38
lib/Devscripts/Salsa/update_user.pm
Normal file
|
@ -0,0 +1,38 @@
|
|||
# Updates user role in a group
|
||||
package Devscripts::Salsa::update_user;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
sub update_user {
|
||||
my ($self, $level, $user) = @_;
|
||||
unless ($level and $user) {
|
||||
ds_warn "Usage $0 update_user <level> <userid>";
|
||||
return 1;
|
||||
}
|
||||
unless ($self->group_id) {
|
||||
ds_warn "Unable to update user without --group-id";
|
||||
return 1;
|
||||
}
|
||||
|
||||
my $id = $self->username2id($user);
|
||||
my $al = $self->levels_name($level);
|
||||
return 1
|
||||
if (
|
||||
$ds_yes < 0
|
||||
and ds_prompt(
|
||||
"You're going to accept $user as $level in group $self->{group_id}. Continue (Y/n) "
|
||||
) =~ refuse
|
||||
);
|
||||
$self->api->update_group_member(
|
||||
$self->group_id,
|
||||
$id,
|
||||
{
|
||||
access_level => $al,
|
||||
});
|
||||
ds_warn "User $user removed from group " . $self->group_id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
24
lib/Devscripts/Salsa/whoami.pm
Normal file
24
lib/Devscripts/Salsa/whoami.pm
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Gives information on token owner
|
||||
package Devscripts::Salsa::whoami;
|
||||
|
||||
use strict;
|
||||
use Devscripts::Output;
|
||||
use Moo::Role;
|
||||
|
||||
sub whoami {
|
||||
my ($self) = @_;
|
||||
my $current_user = $self->api->current_user;
|
||||
print <<END;
|
||||
Id : $current_user->{id}
|
||||
Username: $current_user->{username}
|
||||
Name : $current_user->{name}
|
||||
Email : $current_user->{email}
|
||||
State : $current_user->{state}
|
||||
END
|
||||
$self->cache->{user}->{ $current_user->{id} } = $current_user->{username};
|
||||
$self->cache->{user_id}->{ $current_user->{username} }
|
||||
= $current_user->{id};
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
Loading…
Add table
Add a link
Reference in a new issue