• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jreleaser / jreleaser / #558

08 Dec 2025 02:56PM UTC coverage: 48.239% (+0.02%) from 48.215%
#558

push

github

aalmiray
feat(core): warn when a name template cannot be resolved

Closes #1960

Closes #1961

299 of 573 new or added lines in 133 files covered. (52.18%)

4 existing lines in 4 files now uncovered.

26047 of 53996 relevant lines covered (48.24%)

0.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

7.89
/sdks/jreleaser-gitlab-java-sdk/src/main/java/org/jreleaser/sdk/gitlab/Gitlab.java
1
/*
2
 * SPDX-License-Identifier: Apache-2.0
3
 *
4
 * Copyright 2020-2025 The JReleaser authors.
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     https://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
package org.jreleaser.sdk.gitlab;
19

20
import com.fasterxml.jackson.annotation.JsonInclude;
21
import com.fasterxml.jackson.databind.DeserializationFeature;
22
import com.fasterxml.jackson.databind.ObjectMapper;
23
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
24
import com.fasterxml.jackson.databind.SerializationFeature;
25
import feign.form.FormData;
26
import feign.form.FormEncoder;
27
import feign.jackson.JacksonDecoder;
28
import feign.jackson.JacksonEncoder;
29
import org.apache.tika.Tika;
30
import org.apache.tika.mime.MediaType;
31
import org.jreleaser.bundle.RB;
32
import org.jreleaser.model.api.JReleaserContext;
33
import org.jreleaser.model.spi.release.Asset;
34
import org.jreleaser.model.spi.release.Release;
35
import org.jreleaser.model.spi.release.User;
36
import org.jreleaser.model.spi.upload.UploadException;
37
import org.jreleaser.mustache.TemplateContext;
38
import org.jreleaser.sdk.commons.ClientUtils;
39
import org.jreleaser.sdk.commons.RestAPIException;
40
import org.jreleaser.sdk.gitlab.api.GitlabAPI;
41
import org.jreleaser.sdk.gitlab.api.GlBranch;
42
import org.jreleaser.sdk.gitlab.api.GlFileUpload;
43
import org.jreleaser.sdk.gitlab.api.GlIssue;
44
import org.jreleaser.sdk.gitlab.api.GlLabel;
45
import org.jreleaser.sdk.gitlab.api.GlLink;
46
import org.jreleaser.sdk.gitlab.api.GlLinkRequest;
47
import org.jreleaser.sdk.gitlab.api.GlMilestone;
48
import org.jreleaser.sdk.gitlab.api.GlPackage;
49
import org.jreleaser.sdk.gitlab.api.GlProject;
50
import org.jreleaser.sdk.gitlab.api.GlRelease;
51
import org.jreleaser.sdk.gitlab.api.GlUser;
52
import org.jreleaser.sdk.gitlab.internal.Page;
53
import org.jreleaser.sdk.gitlab.internal.PaginatingDecoder;
54
import org.jreleaser.util.CollectionUtils;
55
import org.jreleaser.util.StringUtils;
56

57
import java.io.IOException;
58
import java.net.URI;
59
import java.net.URISyntaxException;
60
import java.nio.file.Files;
61
import java.nio.file.Path;
62
import java.util.ArrayList;
63
import java.util.Collection;
64
import java.util.LinkedHashMap;
65
import java.util.List;
66
import java.util.Map;
67
import java.util.Optional;
68
import java.util.Set;
69
import java.util.function.Function;
70
import java.util.regex.Matcher;
71
import java.util.regex.Pattern;
72
import java.util.stream.Collectors;
73

74
import static java.util.Objects.requireNonNull;
75
import static org.jreleaser.mustache.Templates.resolveTemplate;
76
import static org.jreleaser.sdk.gitlab.internal.UrlEncoder.urlEncode;
77
import static org.jreleaser.util.StringUtils.isBlank;
78
import static org.jreleaser.util.StringUtils.requireNonBlank;
79

80
/**
81
 * @author Andres Almiray
82
 * @since 0.1.0
83
 */
84
class Gitlab {
85
    static final String ENDPOINT = "https://gitlab.com/api/v4";
86
    private static final String API_V4 = "/api/v4";
87
    private static final String GRAPQL_DELETE_PAYLOAD = "{\n" +
88
        "  \"query\": \"mutation {uploadDelete(input: { secret: \\\"{{secret}}\\\", filename: \\\"{{filename}}\\\", projectPath: \\\"{{projectPath}}\\\"}) { upload { id size path } errors }}\",\n" +
89
        "  \"variables\": null\n" +
90
        "}\n";
91
    private static final Pattern UPLOADS_PATTERN = Pattern.compile("(.*?)/uploads/(.*?)");
1✔
92

93
    private final Tika tika = new Tika();
1✔
94
    private final JReleaserContext context;
95
    private final GitlabAPI api;
96
    private final String apiHost;
97
    private final String graphQlEndpoint;
98
    private final int connectTimeout;
99
    private final int readTimeout;
100

101
    private GlUser user;
102
    private GlProject project;
103

104
    Gitlab(JReleaserContext context,
105
           String endpoint,
106
           String token,
107
           int connectTimeout,
108
           int readTimeout) {
1✔
109
        requireNonNull(context, "'context' must not be null");
1✔
110
        requireNonBlank(token, "'token' must not be blank");
1✔
111

112
        if (isBlank(endpoint)) {
1✔
113
            endpoint = ENDPOINT;
×
114
        }
115

116
        if (!endpoint.endsWith(API_V4)) {
1✔
117
            if (endpoint.endsWith("/")) {
1✔
118
                endpoint = endpoint.substring(0, endpoint.length() - 1);
×
119
            }
120
            endpoint += API_V4;
1✔
121
        }
122

123
        this.apiHost = endpoint.substring(0, endpoint.length() - API_V4.length());
1✔
124
        this.graphQlEndpoint = endpoint.replace("v4", "graphql");
1✔
125
        this.connectTimeout = connectTimeout;
1✔
126
        this.readTimeout = readTimeout;
1✔
127

128
        ObjectMapper objectMapper = new ObjectMapper()
1✔
129
            .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
1✔
130
            .setSerializationInclusion(JsonInclude.Include.NON_NULL)
1✔
131
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
1✔
132
            .configure(SerializationFeature.INDENT_OUTPUT, true);
1✔
133

134
        this.context = context;
1✔
135
        this.api = ClientUtils.builder(context, connectTimeout, readTimeout)
1✔
136
            .encoder(new FormEncoder(new JacksonEncoder(objectMapper)))
1✔
137
            .decoder(new PaginatingDecoder(new JacksonDecoder(objectMapper)))
1✔
138
            .requestInterceptor(template -> template.header("Authorization", String.format("Bearer %s", token)))
1✔
139
            .target(GitlabAPI.class, endpoint);
1✔
140
    }
1✔
141

142
    GlProject findProject(String projectName, String projectIdentifier) throws RestAPIException {
143
        return getProject(projectName, projectIdentifier);
×
144
    }
145

146
    List<Release> listReleases(String owner, String repoName, String projectIdentifier) throws IOException {
147
        context.getLogger().debug(RB.$("git.list.releases"), owner, repoName);
×
148

149
        List<Release> releases = new ArrayList<>();
×
150

151
        if (isBlank(projectIdentifier)) {
×
152
            GlProject project = getProject(repoName, projectIdentifier);
×
153
            projectIdentifier = project.getId().toString();
×
154
        }
155

156
        Page<List<GlRelease>> page = api.listReleases0(projectIdentifier);
×
157
        page.getContent().stream()
×
158
            .map(r -> new Release(
×
159
                r.getName(),
×
160
                r.getTagName(),
×
161
                apiHost + r.getTagPath(),
×
162
                r.getReleasedAt()
×
163
            ))
164
            .forEach(releases::add);
×
165

166
        if (page.hasLinks() && page.getLinks().hasNext()) {
×
167
            try {
168
                collectReleases(page, releases);
×
169
            } catch (URISyntaxException e) {
×
170
                throw new IOException(e);
×
171
            }
×
172
        }
173

174
        return releases;
×
175
    }
176

177
    private void collectReleases(Page<List<GlRelease>> page, List<Release> releases) throws URISyntaxException {
178
        URI next = new URI(page.getLinks().next());
×
179
        context.getLogger().debug(next.toString());
×
180

181
        page = api.listReleases1(next);
×
182
        page.getContent().stream()
×
183
            .map(r -> new Release(
×
184
                r.getName(),
×
185
                r.getTagName(),
×
186
                apiHost + r.getTagPath(),
×
187
                r.getReleasedAt()
×
188
            ))
189
            .forEach(releases::add);
×
190

191
        if (page.hasLinks() && page.getLinks().hasNext()) {
×
192
            collectReleases(page, releases);
×
193
        }
194
    }
×
195

196
    List<String> listBranches(String owner, String repoName, String projectIdentifier) throws IOException {
197
        context.getLogger().debug(RB.$("git.list.branches"), owner, repoName);
×
198

199
        List<String> branches = new ArrayList<>();
×
200

201
        if (isBlank(projectIdentifier)) {
×
202
            GlProject project = getProject(repoName, projectIdentifier);
×
203
            projectIdentifier = project.getId().toString();
×
204
        }
205

206
        Page<List<GlBranch>> page = api.listBranches0(projectIdentifier);
×
207
        page.getContent().stream()
×
208
            .map(GlBranch::getName)
×
209
            .forEach(branches::add);
×
210

211
        if (page.hasLinks() && page.getLinks().hasNext()) {
×
212
            try {
213
                collectBranches(page, branches);
×
214
            } catch (URISyntaxException e) {
×
215
                throw new IOException(e);
×
216
            }
×
217
        }
218

219
        return branches;
×
220
    }
221

222
    private void collectBranches(Page<List<GlBranch>> page, List<String> branches) throws URISyntaxException {
223
        URI next = new URI(page.getLinks().next());
×
224
        context.getLogger().debug(next.toString());
×
225

226
        page = api.listBranches1(next);
×
227
        page.getContent().stream()
×
228
            .map(GlBranch::getName)
×
229
            .forEach(branches::add);
×
230

231
        if (page.hasLinks() && page.getLinks().hasNext()) {
×
232
            collectBranches(page, branches);
×
233
        }
234
    }
×
235

236
    Optional<GlMilestone> findMilestoneByName(String owner, String repo, Integer projectIdentifier, String milestoneName) {
237
        context.getLogger().debug(RB.$("git.milestone.lookup"), milestoneName, owner, repo);
×
238

239
        return findMilestone(projectIdentifier, milestoneName, "active");
×
240
    }
241

242
    Optional<GlMilestone> findClosedMilestoneByName(String owner, String repo, Integer projectIdentifier, String milestoneName) {
243
        context.getLogger().debug(RB.$("git.milestone.lookup.closed"), milestoneName, owner, repo);
×
244

245
        return findMilestone(projectIdentifier, milestoneName, "closed");
×
246
    }
247

248
    private Optional<GlMilestone> findMilestone(Integer projectIdentifier, String milestoneName, String state) {
249
        try {
250
            List<GlMilestone> milestones = api.findMilestoneByTitle(projectIdentifier, CollectionUtils.<String, Object>map()
×
251
                .e("title", milestoneName));
×
252

253
            if (null == milestones || milestones.isEmpty()) {
×
254
                return Optional.empty();
×
255
            }
256

257
            GlMilestone milestone = milestones.get(0);
×
258
            return state.equals(milestone.getState()) ? Optional.of(milestone) : Optional.empty();
×
259
        } catch (RestAPIException e) {
×
260
            if (e.isNotFound() || e.isForbidden()) {
×
261
                // ok
262
                return Optional.empty();
×
263
            }
264
            throw e;
×
265
        }
266
    }
267

268
    void closeMilestone(String owner, String repo, Integer projectIdentifier, GlMilestone milestone) {
269
        context.getLogger().debug(RB.$("git.milestone.close"), milestone.getTitle(), owner, repo);
×
270

271
        api.updateMilestone(CollectionUtils.<String, Object>map()
×
272
                .e("state_event", "close"),
×
273
            projectIdentifier, milestone.getId());
×
274
    }
×
275

276
    GlProject createProject(String owner, String repo) {
277
        context.getLogger().debug(RB.$("git.project.create"), owner, repo);
×
278

279
        return api.createProject(repo, "public");
×
280
    }
281

282
    GlUser getCurrentUser() throws RestAPIException {
283
        if (null == user) {
×
284
            context.getLogger().debug(RB.$("git.fetch.current.user"));
×
285
            user = api.getCurrentUser();
×
286
        }
287

288
        return user;
×
289
    }
290

291
    GlProject getProject(String projectName, String projectIdentifier) throws RestAPIException {
292
        if (null == project) {
×
293
            if (StringUtils.isNotBlank(projectIdentifier)) {
×
294
                context.getLogger().debug(RB.$("git.fetch.gitlab.project_by_id"), projectIdentifier);
×
295
                project = api.getProject(projectIdentifier.trim());
×
296
            } else {
297
                GlUser u = getCurrentUser();
×
298

299
                context.getLogger().debug(RB.$("git.fetch.gitlab.project.by.user"), projectName, u.getUsername(), u.getId());
×
300
                List<GlProject> projects = api.getProject(u.getId(), CollectionUtils.<String, Object>map()
×
301
                    .e("search", projectName));
×
302

303
                if (null == projects || projects.isEmpty()) {
×
304
                    throw new RestAPIException(404, RB.$("ERROR_project_not_exist", projectName));
×
305
                }
306

307
                project = projects.get(0);
×
308
            }
309

310
            context.getLogger().debug(RB.$("git.gitlab.project.found"), project.getNameWithNamespace(), project.getId());
×
311
        }
312

313
        return project;
×
314
    }
315

316
    GlRelease findReleaseByTag(String owner, String repoName, String projectIdentifier, String tagName) throws RestAPIException {
317
        context.getLogger().debug(RB.$("git.fetch.release.by.tag"), owner, repoName, tagName);
×
318

319
        GlProject project = getProject(repoName, projectIdentifier);
×
320

321
        try {
322
            return api.getRelease(project.getId(), urlEncode(tagName));
×
323
        } catch (RestAPIException e) {
×
324
            if (e.isNotFound() || e.isForbidden()) {
×
325
                // ok
326
                return null;
×
327
            }
328
            throw e;
×
329
        }
330
    }
331

332
    void deleteTag(String owner, String repoName, Integer projectIdentifier, String tagName) throws RestAPIException {
333
        context.getLogger().debug(RB.$("git.delete.tag.from"), tagName, owner, repoName);
×
334

335
        api.deleteTag(projectIdentifier, urlEncode(tagName));
×
336
    }
×
337

338
    void deletePackage(Integer projectIdentifier, Integer packageId) throws RestAPIException {
339
        context.getLogger().debug(RB.$("gitlab.delete.package"), packageId, projectIdentifier);
×
340

341
        api.deletePackage(projectIdentifier, packageId);
×
342
    }
×
343

344
    void deleteRelease(String owner, String repoName, String projectIdentifier, String tagName) throws RestAPIException {
345
        context.getLogger().debug(RB.$("git.delete.release.from"), tagName, owner, repoName);
×
346

347
        GlProject project = getProject(repoName, projectIdentifier);
×
348

349
        api.deleteRelease(project.getId(), urlEncode(tagName));
×
350
    }
×
351

352
    void createRelease(String owner, String repoName, Integer projectIdentifier, GlRelease release) throws RestAPIException {
353
        context.getLogger().debug(RB.$("git.create.release"), owner, repoName, release.getTagName());
×
354

355
        api.createRelease(release, projectIdentifier);
×
356
    }
×
357

358
    void updateRelease(String owner, String repoName, String projectIdentifier, GlRelease release) throws RestAPIException {
359
        context.getLogger().debug(RB.$("git.update.release"), owner, repoName, release.getTagName());
×
360

361
        GlProject project = getProject(repoName, projectIdentifier);
×
362

363
        api.updateRelease(release, project.getId());
×
364
    }
×
365

366
    Collection<GlFileUpload> uploadAssets(String owner, String repoName, Integer projectIdentifier, Set<Asset> assets) throws IOException, RestAPIException {
367
        context.getLogger().debug(RB.$("git.upload.assets"), owner, repoName);
×
368

369
        List<GlFileUpload> uploads = new ArrayList<>();
×
370

371
        for (Asset asset : assets) {
×
372
            if (0 == Files.size(asset.getPath()) || !Files.exists(asset.getPath())) {
×
373
                // do not upload empty or non existent files
374
                continue;
×
375
            }
376

377
            context.getLogger().info(" " + RB.$("git.upload.asset"), asset.getFilename());
×
378
            try {
379
                GlFileUpload upload = api.uploadFile(projectIdentifier, toFormData(asset.getPath()));
×
380
                upload.setName(asset.getFilename());
×
381
                uploads.add(upload);
×
382
            } catch (IOException | RestAPIException e) {
×
383
                context.getLogger().error(" " + RB.$("git.upload.asset.failure"), asset.getFilename());
×
384
                throw e;
×
385
            }
×
386
        }
×
387

388
        return uploads;
×
389
    }
390

391
    void linkReleaseAssets(String owner, String repoName, GlRelease release, Integer projectIdentifier, Collection<GlFileUpload> uploads) throws RestAPIException {
392
        context.getLogger().info(RB.$("git.upload.asset.links"), owner, repoName, release.getTagName());
×
393

394
        for (GlFileUpload upload : uploads) {
×
395
            context.getLogger().info(" " + RB.$("git.upload.asset.link"), upload.getName());
×
396
            try {
397
                api.linkAsset(upload.toLinkRequest(apiHost), projectIdentifier, release.getTagName());
×
398
            } catch (RestAPIException e) {
×
399
                context.getLogger().error(" " + RB.$("git.upload.asset.link.failure"), upload.getName());
×
400
                throw e;
×
401
            }
×
402
        }
×
403
    }
×
404

405
    void linkAssets(String owner, String repoName, GlRelease release, Integer projectIdentifier, Collection<GlLinkRequest> links) throws RestAPIException {
406
        context.getLogger().info(RB.$("git.upload.asset.links"), owner, repoName, release.getTagName());
×
407

408
        for (GlLinkRequest link : links) {
×
409
            context.getLogger().info(" " + RB.$("git.upload.asset.link"), link.getName());
×
410
            try {
411
                api.linkAsset(link, projectIdentifier, release.getTagName());
×
412
            } catch (RestAPIException e) {
×
413
                context.getLogger().error(" " + RB.$("git.upload.asset.link.failure"), link.getName());
×
414
                throw e;
×
415
            }
×
416
        }
×
417
    }
×
418

419
    Optional<User> findUser(String email, String name) throws RestAPIException {
420
        context.getLogger().debug(RB.$("git.user.lookup"), name, email);
×
421

422
        List<GlUser> users = api.searchUser(CollectionUtils.<String, String>mapOf("scope", "users", "search", email));
×
423
        if (null != users && !users.isEmpty()) {
×
424
            GlUser user = users.get(0);
×
425
            return Optional.of(new User(user.getUsername(), email, user.getWebUrl()));
×
426
        }
427

428
        users = api.searchUser(CollectionUtils.<String, String>mapOf("scope", "users", "search", name));
×
429
        if (null != users && !users.isEmpty()) {
×
430
            GlUser user = users.get(0);
×
431
            if (name.equals(user.getName())) {
×
432
                return Optional.of(new User(user.getUsername(), email, user.getWebUrl()));
×
433
            }
434
        }
435

436
        return Optional.empty();
×
437
    }
438

439
    GlLabel getOrCreateLabel(Integer projectIdentifier, String labelName, String labelColor, String description) throws IOException {
440
        context.getLogger().debug(RB.$("git.label.fetch", labelName));
×
441

442
        List<GlLabel> labels = listLabels(projectIdentifier);
×
443
        Optional<GlLabel> label = labels.stream()
×
444
            .filter(l -> l.getName().equals(labelName))
×
445
            .findFirst();
×
446

447
        if (label.isPresent()) {
×
448
            return label.get();
×
449
        }
450

451
        context.getLogger().debug(RB.$("git.label.create", labelName));
×
452
        return api.createLabel(projectIdentifier, labelName, labelColor, description);
×
453
    }
454

455
    void addLabelToIssue(Integer projectIdentifier, GlIssue issue, GlLabel label) {
456
        context.getLogger().debug(RB.$("git.issue.label", label.getName(), issue.getIid()));
×
457

458
        Map<String, Object> params = new LinkedHashMap<>();
×
459
        List<String> list = (List<String>) params.computeIfAbsent("labels", k -> new ArrayList<>());
×
460
        list.addAll(issue.getLabels());
×
461
        list.add(label.getName());
×
462

463
        api.updateIssue(params, projectIdentifier, issue.getIid());
×
464
    }
×
465

466
    void commentOnIssue(Integer projectIdentifier, GlIssue issue, String comment) {
467
        context.getLogger().debug(RB.$("git.issue.comment", issue.getIid()));
×
468

469
        Map<String, String> params = new LinkedHashMap<>();
×
470
        params.put("body", comment);
×
471

472
        api.commentIssue(params, projectIdentifier, issue.getIid());
×
473
    }
×
474

475
    void setMilestoneOnIssue(Integer projectIdentifier, GlIssue issue, GlMilestone milestone) {
476
        Map<String, Object> params = new LinkedHashMap<>();
×
477
        params.put("milestone_id", milestone.getId());
×
478

479
        api.updateIssue(params, projectIdentifier, issue.getIid());
×
480
    }
×
481

482
    List<GlLabel> listLabels(Integer projectIdentifier) throws IOException {
483
        context.getLogger().debug(RB.$("gitlab.list.labels"), projectIdentifier);
×
484

485
        List<GlLabel> labels = new ArrayList<>();
×
486
        Page<List<GlLabel>> page = api.listLabels0(projectIdentifier);
×
487
        labels.addAll(page.getContent());
×
488

489
        if (page.hasLinks() && page.getLinks().hasNext()) {
×
490
            try {
491
                collectLabels(page, labels);
×
492
            } catch (URISyntaxException e) {
×
493
                throw new IOException(e);
×
494
            }
×
495
        }
496

497
        return labels;
×
498
    }
499

500
    private void collectLabels(Page<List<GlLabel>> page, List<GlLabel> labels) throws URISyntaxException {
501
        URI next = new URI(page.getLinks().next());
×
502
        context.getLogger().debug(next.toString());
×
503

504
        page = api.listLabels1(next);
×
505
        labels.addAll(page.getContent());
×
506

507
        if (page.hasLinks() && page.getLinks().hasNext()) {
×
508
            collectLabels(page, labels);
×
509
        }
510
    }
×
511

512
    List<GlIssue> listIssues(Integer projectIdentifier) throws IOException {
513
        context.getLogger().debug(RB.$("gitlab.list.issues"), projectIdentifier);
×
514

515
        List<GlIssue> issues = new ArrayList<>();
×
516
        Page<List<GlIssue>> page = api.listIssues0(projectIdentifier);
×
517
        issues.addAll(page.getContent());
×
518

519
        if (page.hasLinks() && page.getLinks().hasNext()) {
×
520
            try {
521
                collectIssues(page, issues);
×
522
            } catch (URISyntaxException e) {
×
523
                throw new IOException(e);
×
524
            }
×
525
        }
526

527
        return issues;
×
528
    }
529

530
    private void collectIssues(Page<List<GlIssue>> page, List<GlIssue> issues) throws URISyntaxException {
531
        URI next = new URI(page.getLinks().next());
×
532
        context.getLogger().debug(next.toString());
×
533

534
        page = api.listIssues1(next);
×
535
        issues.addAll(page.getContent());
×
536

537
        if (page.hasLinks() && page.getLinks().hasNext()) {
×
538
            collectIssues(page, issues);
×
539
        }
540
    }
×
541

542
    List<GlPackage> listPackages(Integer projectIdentifier, String packageType) throws IOException {
543
        context.getLogger().debug(RB.$("gitlab.list.packages"), projectIdentifier);
×
544

545
        Map<String, Object> params = new LinkedHashMap<>();
×
546
        params.put("package_type", packageType);
×
547

548
        List<GlPackage> packages = new ArrayList<>();
×
549
        Page<List<GlPackage>> page = api.listPackages0(projectIdentifier, params);
×
550
        packages.addAll(page.getContent());
×
551

552
        if (page.hasLinks() && page.getLinks().hasNext()) {
×
553
            try {
554
                collectPackages(page, packages);
×
555
            } catch (URISyntaxException e) {
×
556
                throw new IOException(e);
×
557
            }
×
558
        }
559

560
        return packages;
×
561
    }
562

563
    private void collectPackages(Page<List<GlPackage>> page, List<GlPackage> packages) throws URISyntaxException {
564
        URI next = new URI(page.getLinks().next());
×
565
        context.getLogger().debug(next.toString());
×
566

567
        page = api.listPackages1(next);
×
568
        packages.addAll(page.getContent());
×
569

570
        if (page.hasLinks() && page.getLinks().hasNext()) {
×
571
            collectPackages(page, packages);
×
572
        }
573
    }
×
574

575
    Map<String, GlLink> listLinks(Integer projectIdentifier, String tagName) throws IOException {
576
        context.getLogger().debug(RB.$("gitlab.list.links"), tagName, projectIdentifier);
×
577

578
        List<GlLink> links = new ArrayList<>();
×
579
        Page<List<GlLink>> page = api.listLinks0(projectIdentifier, tagName);
×
580
        links.addAll(page.getContent());
×
581

582
        if (page.hasLinks() && page.getLinks().hasNext()) {
×
583
            try {
584
                collectLinks(page, links);
×
585
            } catch (URISyntaxException e) {
×
586
                throw new IOException(e);
×
587
            }
×
588
        }
589

590
        return links.stream()
×
591
            .collect(Collectors.toMap(GlLink::getName, Function.identity()));
×
592
    }
593

594
    private void collectLinks(Page<List<GlLink>> page, List<GlLink> links) throws URISyntaxException {
595
        URI next = new URI(page.getLinks().next());
×
596
        context.getLogger().debug(next.toString());
×
597

598
        page = api.listLinks1(next);
×
599
        links.addAll(page.getContent());
×
600

601
        if (page.hasLinks() && page.getLinks().hasNext()) {
×
602
            collectLinks(page, links);
×
603
        }
604
    }
×
605

606
    void deleteLinkedAsset(String token, Integer projectIdentifier, String tagName, GlLink link) throws IOException {
607
        context.getLogger().info(" " + RB.$("git.delete.asset"), link.getName());
×
608

609
        Map<String, String> headers = new LinkedHashMap<>();
×
610
        headers.put("Authorization", "Bearer " + token);
×
611

612
        context.getLogger().debug(RB.$("gitlab.delete.file", link.getName()));
×
613
        try {
614
            TemplateContext props = new TemplateContext();
×
615
            props.set("filename", link.getName());
×
616

617
            String url = link.getUrl().substring(apiHost.length() + 1);
×
618
            url = url.substring(0, url.length() - link.getName().length() - 1);
×
619
            Matcher matcher = UPLOADS_PATTERN.matcher(url);
×
620
            if (matcher.matches()) {
×
621
                props.set("projectPath", matcher.group(1));
×
622
                props.set("secret", matcher.group(2));
×
623
            } else {
624
                throw new IOException(RB.$("ERROR_gitlab_invalid_upload_link", link.getUrl()));
×
625
            }
626

NEW
627
            String payload = resolveTemplate(context.getLogger(), GRAPQL_DELETE_PAYLOAD, props);
×
628

629
            FormData data = ClientUtils.toFormData(
×
630
                "payload",
631
                headers.computeIfAbsent("Content-Type", k -> "text/plain"),
×
632
                payload);
633

634
            ClientUtils.postFile(context.getLogger(),
×
635
                graphQlEndpoint,
636
                connectTimeout,
637
                readTimeout,
638
                data,
639
                headers);
640
        } catch (UploadException e) {
×
641
            if (e.getCause() instanceof IOException) throw (IOException) e.getCause();
×
642
            throw new IOException(e);
×
643
        }
×
644

645
        context.getLogger().debug(RB.$("gitlab.delete.link", link.getId(), link.getName()));
×
646
        api.deleteLink(projectIdentifier, tagName, link.getId());
×
647
    }
×
648

649
    private FormData toFormData(Path asset) throws IOException {
650
        return FormData.builder()
×
651
            .fileName(asset.getFileName().toString())
×
652
            .contentType(MediaType.parse(tika.detect(asset)).toString())
×
653
            .data(Files.readAllBytes(asset))
×
654
            .build();
×
655
    }
656
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc