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

jreleaser / jreleaser / #524

01 Aug 2025 02:16PM UTC coverage: 48.454% (-0.9%) from 49.315%
#524

push

github

aalmiray
fix(hooks): Inherit matrix and environment from named groups

Relates to #1947

56 of 125 new or added lines in 5 files covered. (44.8%)

450 existing lines in 37 files now uncovered.

25680 of 52999 relevant lines covered (48.45%)

0.48 hits per line

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

21.82
/sdks/jreleaser-github-java-sdk/src/main/java/org/jreleaser/sdk/github/Github.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.github;
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.httpclient.ApacheHttpClient;
28
import feign.jackson.JacksonDecoder;
29
import feign.jackson.JacksonEncoder;
30
import org.jreleaser.bundle.RB;
31
import org.jreleaser.model.api.JReleaserContext;
32
import org.jreleaser.model.spi.release.Asset;
33
import org.jreleaser.model.spi.release.Release;
34
import org.jreleaser.model.spi.release.User;
35
import org.jreleaser.model.spi.upload.UploadException;
36
import org.jreleaser.sdk.commons.ClientUtils;
37
import org.jreleaser.sdk.commons.RestAPIException;
38
import org.jreleaser.sdk.github.api.GhAsset;
39
import org.jreleaser.sdk.github.api.GhAttachment;
40
import org.jreleaser.sdk.github.api.GhBranch;
41
import org.jreleaser.sdk.github.api.GhDiscussion;
42
import org.jreleaser.sdk.github.api.GhIssue;
43
import org.jreleaser.sdk.github.api.GhLabel;
44
import org.jreleaser.sdk.github.api.GhMilestone;
45
import org.jreleaser.sdk.github.api.GhOrganization;
46
import org.jreleaser.sdk.github.api.GhPackageVersion;
47
import org.jreleaser.sdk.github.api.GhRelease;
48
import org.jreleaser.sdk.github.api.GhReleaseNotes;
49
import org.jreleaser.sdk.github.api.GhReleaseNotesParams;
50
import org.jreleaser.sdk.github.api.GhRepository;
51
import org.jreleaser.sdk.github.api.GhSearchUser;
52
import org.jreleaser.sdk.github.api.GhTag;
53
import org.jreleaser.sdk.github.api.GhUser;
54
import org.jreleaser.sdk.github.api.GithubAPI;
55
import org.jreleaser.sdk.github.internal.Page;
56
import org.jreleaser.sdk.github.internal.PaginatingDecoder;
57
import org.jreleaser.util.CollectionUtils;
58

59
import java.io.IOException;
60
import java.io.Reader;
61
import java.net.URI;
62
import java.net.URISyntaxException;
63
import java.nio.file.Files;
64
import java.util.ArrayList;
65
import java.util.LinkedHashMap;
66
import java.util.List;
67
import java.util.Map;
68
import java.util.Optional;
69
import java.util.Set;
70

71
import static java.util.Objects.requireNonNull;
72
import static java.util.stream.Collectors.toList;
73
import static org.jreleaser.sdk.commons.ClientUtils.toFormData;
74
import static org.jreleaser.util.StringUtils.requireNonBlank;
75

76
/**
77
 * @author Andres Almiray
78
 * @since 0.1.0
79
 */
80
class Github {
81
    private static final String USERS_NOREPLY_GITHUB_COM = "@users.noreply.github.com";
82
    private static final String ENDPOINT = "https://api.github.com";
83
    private static final String GITHUB_API_VERSION = "2022-11-28";
84
    private static final String GITHUB_MIME_TYPE = "application/vnd.github+json";
85

86
    private final JReleaserContext context;
87
    private final ObjectMapper objectMapper;
88
    private final GithubAPI api;
89
    private final String token;
90
    private final int connectTimeout;
91
    private final int readTimeout;
92

93
    Github(JReleaserContext context,
94
           String token,
95
           int connectTimeout,
96
           int readTimeout) {
97
        this(context, ENDPOINT, token, connectTimeout, readTimeout);
×
98
    }
×
99

100
    Github(JReleaserContext context,
101
           String endpoint,
102
           String token,
103
           int connectTimeout,
104
           int readTimeout) {
1✔
105
        this.context = requireNonNull(context, "'context' must not be null");
1✔
106
        this.token = requireNonBlank(token, "'token' must not be blank");
1✔
107
        this.connectTimeout = connectTimeout;
1✔
108
        this.readTimeout = readTimeout;
1✔
109
        requireNonBlank(endpoint, "'endpoint' must not be blank");
1✔
110

111
        if (endpoint.endsWith("/")) {
1✔
112
            endpoint = endpoint.substring(0, endpoint.length() - 1);
×
113
        }
114

115
        this.objectMapper = new ObjectMapper()
1✔
116
            .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)
1✔
117
            .setSerializationInclusion(JsonInclude.Include.NON_NULL)
1✔
118
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
1✔
119
            .configure(SerializationFeature.INDENT_OUTPUT, true);
1✔
120

121
        this.api = ClientUtils.builder(context, connectTimeout, readTimeout)
1✔
122
            .client(new ApacheHttpClient())
1✔
123
            .encoder(new FormEncoder(new JacksonEncoder(objectMapper)))
1✔
124
            .decoder(new PaginatingDecoder(new JacksonDecoder(objectMapper)))
1✔
125
            .requestInterceptor(template -> {
1✔
126
                template.header("Accept", GITHUB_MIME_TYPE);
1✔
127
                template.header("X-GitHub-Api-Version", GITHUB_API_VERSION);
1✔
128
                template.header("Authorization", String.format("Bearer %s", token));
1✔
129
            })
1✔
130
            .target(GithubAPI.class, endpoint);
1✔
131
    }
1✔
132

133
    GhRepository findRepository(String owner, String repo) {
134
        context.getLogger().debug(RB.$("git.repository.lookup"), owner, repo);
1✔
135
        try {
136
            return api.getRepository(owner, repo);
1✔
UNCOV
137
        } catch (RestAPIException e) {
×
UNCOV
138
            if (e.isNotFound()) {
×
139
                // ok
UNCOV
140
                return null;
×
141
            }
142
            throw e;
×
143
        }
144
    }
145

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

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

151
        int pageCount = 0;
1✔
152
        Map<String, Object> params = new LinkedHashMap<>();
1✔
153
        params.put("draft", false);
1✔
154
        params.put("prerelease", false);
1✔
155
        params.put("per_page", 20);
1✔
156

157
        boolean consume = true;
1✔
158
        do {
159
            params.put("page", ++pageCount);
1✔
160
            Page<List<GhRelease>> page = api.listReleases(owner, repoName, params);
1✔
161
            page.getContent().stream()
1✔
162
                .map(r -> new Release(
1✔
163
                    r.getName(),
×
164
                    r.getTagName(),
×
165
                    r.getHtmlUrl(),
×
166
                    r.getPublishedAt()
×
167
                ))
168
                .forEach(releases::add);
1✔
169

170
            if (!page.hasLinks() || !page.getLinks().hasNext()) {
1✔
171
                consume = false;
1✔
172
            }
173
        }
174
        while (consume);
1✔
175

176
        return releases;
1✔
177
    }
178

179
    List<String> listBranches(String owner, String repoName) {
180
        context.getLogger().debug(RB.$("git.list.branches"), owner, repoName);
×
181

182
        List<String> branches = new ArrayList<>();
×
183

184
        int pageCount = 0;
×
185
        Map<String, Object> params = new LinkedHashMap<>();
×
186
        params.put("per_page", 20);
×
187

188
        boolean consume = true;
×
189
        do {
190
            params.put("page", ++pageCount);
×
191
            Page<List<GhBranch>> page = api.listBranches(owner, repoName, params);
×
192
            page.getContent().stream()
×
193
                .map(GhBranch::getName)
×
194
                .forEach(branches::add);
×
195

196
            if (!page.hasLinks() || !page.getLinks().hasNext()) {
×
197
                consume = false;
×
198
            }
199
        }
200
        while (consume);
×
201

202
        return branches;
×
203
    }
204

205
    Map<String, GhAsset> listAssets(String owner, String repo, GhRelease release) {
206
        context.getLogger().debug(RB.$("git.list.assets.github"), owner, repo, release.getId());
×
207

208
        Map<String, GhAsset> assets = new LinkedHashMap<>();
×
209
        for (GhAsset asset : api.listAssets(owner, repo, release.getId())) {
×
210
            assets.put(asset.getName(), asset);
×
211
        }
×
212

213
        return assets;
×
214
    }
215

216
    List<GhMilestone> listMilestones(String owner, String repoName, String state) {
217
        context.getLogger().debug(RB.$("git.list.milestones"), owner, repoName, state);
×
218

219
        List<GhMilestone> milestones = new ArrayList<>();
×
220

221
        int pageCount = 0;
×
222
        Map<String, Object> params = new LinkedHashMap<>();
×
223
        params.put("state", state);
×
224
        params.put("per_page", 20);
×
225

226
        boolean consume = true;
×
227
        do {
228
            params.put("page", ++pageCount);
×
229
            Page<List<GhMilestone>> page = api.listMilestones(owner, repoName, params);
×
230
            milestones.addAll(page.getContent());
×
231

232
            if (!page.hasLinks() || !page.getLinks().hasNext()) {
×
233
                consume = false;
×
234
            }
235
        }
236
        while (consume);
×
237

238
        return milestones;
×
239
    }
240

241
    Optional<GhMilestone> findMilestoneByName(String owner, String repo, String milestoneName) {
242
        context.getLogger().debug(RB.$("git.milestone.lookup"), milestoneName, owner, repo);
×
243

244
        return findMilestone(owner, repo, milestoneName, "open");
×
245
    }
246

247
    Optional<GhMilestone> findClosedMilestoneByName(String owner, String repo, String milestoneName) {
248
        context.getLogger().debug(RB.$("git.milestone.lookup.closed"), milestoneName, owner, repo);
×
249

250
        return findMilestone(owner, repo, milestoneName, "closed");
×
251
    }
252

253
    private Optional<GhMilestone> findMilestone(String owner, String repo, String milestoneName, String state) {
254
        return listMilestones(owner, repo, state).stream()
×
255
            .filter(m -> milestoneName.equals(m.getTitle()))
×
256
            .findFirst();
×
257
    }
258

259
    void closeMilestone(String owner, String repo, GhMilestone milestone) {
260
        context.getLogger().debug(RB.$("git.milestone.close"), milestone.getTitle(), owner, repo);
×
261

262
        api.updateMilestone(CollectionUtils.<String, Object>map()
×
263
            .e("state", "closed"), owner, repo, milestone.getNumber());
×
264
    }
×
265

266
    GhRepository createRepository(String owner, String repo) {
UNCOV
267
        context.getLogger().debug(RB.$("git.repository.create"), owner, repo);
×
268

UNCOV
269
        Map<String, Object> params = new LinkedHashMap<>();
×
UNCOV
270
        params.put("name", repo);
×
UNCOV
271
        params.put("private", false);
×
272

UNCOV
273
        GhOrganization organization = resolveOrganization(owner);
×
UNCOV
274
        if (null != organization) {
×
UNCOV
275
            return api.createRepository(params, owner);
×
276
        }
277

278
        return api.createRepository(params);
×
279
    }
280

281
    List<GhTag> listTags(String owner, String repoName) {
282
        context.getLogger().debug(RB.$("git.list.milestones"), owner, repoName);
×
283

284
        List<GhTag> tags = new ArrayList<>();
×
285

286
        int pageCount = 0;
×
287
        Map<String, Object> params = new LinkedHashMap<>();
×
288
        params.put("per_page", 20);
×
289

290
        boolean consume = true;
×
291
        do {
292
            params.put("page", ++pageCount);
×
293
            Page<List<GhTag>> page = api.listTags(owner, repoName, params);
×
294
            tags.addAll(page.getContent());
×
295

296
            if (!page.hasLinks() || !page.getLinks().hasNext()) {
×
297
                consume = false;
×
298
            }
299
        }
300
        while (consume);
×
301

302
        return tags;
×
303
    }
304

305
    GhRelease findReleaseByTag(String owner, String repo, String tagName) {
306
        context.getLogger().debug(RB.$("git.fetch.release.by.tag"), owner, repo, tagName);
×
307

308
        try {
309
            return api.getReleaseByTagName(owner, repo, tagName);
×
310
        } catch (RestAPIException e) {
×
311
            if (e.isNotFound()) {
×
312
                // ok
313
                return null;
×
314
            }
315
            throw e;
×
316
        }
317
    }
318

319
    GhRelease findReleaseById(String owner, String repo, Long id) {
320
        context.getLogger().debug(RB.$("git.fetch.release.by.id"), owner, repo, id);
×
321

322
        return api.getRelease(owner, repo, id);
×
323
    }
324

325
    void deleteTag(String owner, String repo, String tagName) throws RestAPIException {
326
        context.getLogger().debug(RB.$("git.delete.tag.from"), tagName, owner, repo);
1✔
327

328
        try {
329
            api.deleteTag(owner, repo, tagName);
1✔
330
        } catch (RestAPIException e) {
1✔
331
            if (e.isNotFound()) {
1✔
332
                context.getLogger().debug(RB.$("git.tag.not.exist"), tagName);
1✔
333
            }
334
        }
1✔
335
    }
1✔
336

337
    GhRelease createRelease(String owner, String repo, GhRelease release) throws RestAPIException {
338
        context.getLogger().debug(RB.$("git.create.release"), owner, repo, release.getTagName());
×
339

340
        return api.createRelease(release, owner, repo);
×
341
    }
342

343
    void updateRelease(String owner, String repo, Long id, GhRelease release) throws RestAPIException {
344
        context.getLogger().debug(RB.$("git.update.release"), owner, repo, release.getTagName());
×
345

346
        api.updateRelease(release, owner, repo, id);
×
347
    }
×
348

349
    void deleteRelease(String owner, String repo, String tagName, Long id) throws RestAPIException {
350
        context.getLogger().debug(RB.$("git.delete.release.from.id"), tagName, owner, repo, id);
×
351

352
        try {
353
            api.deleteRelease(owner, repo, id);
×
354
        } catch (RestAPIException e) {
×
355
            if (e.isNotFound()) {
×
356
                // OK. Release might have been deleted but
357
                // tag still exists.
358
                return;
×
359
            }
360
            throw e;
×
361
        }
×
362
    }
×
363

364
    void uploadAssets(GhRelease release, Set<Asset> assets) throws IOException {
365
        for (Asset asset : assets) {
×
366
            if (0 == Files.size(asset.getPath()) || !Files.exists(asset.getPath())) {
×
367
                // do not upload empty or non existent files
368
                continue;
×
369
            }
370

371
            uploadOrUpdateAsset(asset, release, "git.upload.asset", "git.upload.asset.failure");
×
372
        }
×
373
    }
×
374

375
    void updateAssets(String owner, String repo, GhRelease release, Set<Asset> assets, Map<String, GhAsset> existingAssets) throws IOException {
376
        for (Asset asset : assets) {
×
377
            if (0 == Files.size(asset.getPath()) || !Files.exists(asset.getPath())) {
×
378
                // do not upload empty or non existent files
379
                continue;
×
380
            }
381

382
            context.getLogger().debug(" " + RB.$("git.delete.asset"), asset.getFilename());
×
383
            try {
384
                api.deleteAsset(owner, repo, existingAssets.get(asset.getFilename()).getId());
×
385
            } catch (RestAPIException e) {
×
386
                context.getLogger().error(" " + RB.$("git.delete.asset.failure"), asset.getFilename());
×
387
                throw e;
×
388
            }
×
389

390
            uploadOrUpdateAsset(asset, release, "git.update.asset", "git.update.asset.failure");
×
391
        }
×
392
    }
×
393

394
    private void uploadOrUpdateAsset(Asset asset, GhRelease release, String operationMessageKey, String operationErrorMessageKey) throws IOException {
395
        context.getLogger().info(" " + RB.$(operationMessageKey), asset.getFilename());
×
396

397
        try {
398
            String uploadUrl = release.getUploadUrl();
×
399
            if (uploadUrl.endsWith("{?name,label}")) {
×
400
                uploadUrl = uploadUrl.substring(0, uploadUrl.length() - 13);
×
401
            }
402

403
            URI uri = new URI(uploadUrl + "?name=" + asset.getFilename());
×
404
            GhAttachment attachment = uploadAsset(uri, toFormData(asset.getPath()));
×
405
            if (!"uploaded".equalsIgnoreCase(attachment.getState())) {
×
406
                context.getLogger().warn(" " + RB.$(operationErrorMessageKey), asset.getFilename());
×
407
            }
408
        } catch (URISyntaxException shouldNeverHappen) {
×
409
            context.getLogger().error(" " + RB.$(operationErrorMessageKey), asset.getFilename());
×
410
            throw new IllegalStateException(RB.$("ERROR_unexpected_error"), shouldNeverHappen);
×
411
        } catch (RestAPIException e) {
×
412
            context.getLogger().error(" " + RB.$(operationErrorMessageKey), asset.getFilename());
×
413
            throw e;
×
414
        } catch (UploadException e) {
×
415
            context.getLogger().error(" " + RB.$(operationErrorMessageKey), asset.getFilename());
×
416
            if (e.getCause() instanceof IOException) throw (IOException) e.getCause();
×
417
            throw new IOException(e);
×
418
        }
×
419
    }
×
420

421
    private GhAttachment uploadAsset(URI uri, FormData data) throws UploadException, IOException {
422
        Map<String, String> headers = new LinkedHashMap<>();
×
423
        headers.put("Accept", GITHUB_MIME_TYPE);
×
424
        headers.put("X-GitHub-Api-Version", GITHUB_API_VERSION);
×
425
        headers.put("Authorization", String.format("Bearer %s", token));
×
426

427
        Reader reader = ClientUtils.postFile(context.getLogger(),
×
428
            uri,
429
            connectTimeout,
430
            readTimeout,
431
            data,
432
            headers);
433

434
        return objectMapper.readValue(reader, GhAttachment.class);
×
435
    }
436

437
    Optional<GhDiscussion> findDiscussion(String organization, String team, String title) {
438
        return listDiscussions(organization, team).stream()
×
439
            .filter(d -> title.equals(d.getTitle()))
×
440
            .findFirst();
×
441
    }
442

443
    List<GhDiscussion> listDiscussions(String organization, String team) {
444
        context.getLogger().debug(RB.$("git.list.discussions"), organization, team);
×
445

446
        List<GhDiscussion> discussions = new ArrayList<>();
×
447

448
        int pageCount = 0;
×
449
        Map<String, Object> params = new LinkedHashMap<>();
×
450
        params.put("per_page", 20);
×
451

452
        boolean consume = true;
×
453
        do {
454
            params.put("page", ++pageCount);
×
455
            Page<List<GhDiscussion>> page = api.listDiscussions(organization, team, params);
×
456
            discussions.addAll(page.getContent());
×
457

458
            if (!page.hasLinks() || !page.getLinks().hasNext()) {
×
459
                consume = false;
×
460
            }
461
        }
462
        while (consume);
×
463

464
        return discussions;
×
465
    }
466

467
    void createDiscussion(String organization, String team, String title, String message) {
468
        context.getLogger().debug(RB.$("git.releaser.discussion.create"), title);
×
469

470
        GhDiscussion discussion = new GhDiscussion();
×
471
        discussion.setTitle(title);
×
472
        discussion.setBody(message);
×
473

474
        api.createDiscussion(discussion, organization, team);
×
475
    }
×
476

477
    GhLabel getOrCreateLabel(String owner, String name, String labelName, String labelColor, String description) {
478
        context.getLogger().debug(RB.$("git.label.fetch", labelName));
×
479

480
        List<GhLabel> labels = listLabels(owner, name);
×
481
        Optional<GhLabel> label = labels.stream()
×
482
            .filter(l -> l.getName().equals(labelName))
×
483
            .findFirst();
×
484

485
        if (label.isPresent()) {
×
486
            return label.get();
×
487
        }
488

489
        context.getLogger().debug(RB.$("git.label.create", labelName));
×
490
        return api.createLabel(owner, name, labelName, labelColor, description);
×
491
    }
492

493
    public Optional<GhIssue> findIssue(String owner, String name, int issueNumber) {
494
        context.getLogger().debug(RB.$("git.issue.fetch", issueNumber));
×
495
        try {
496
            return Optional.of(api.findIssue(owner, name, issueNumber));
×
497
        } catch (RestAPIException e) {
×
498
            if (e.isNotFound()) {
×
499
                return Optional.empty();
×
500
            }
501
            throw e;
×
502
        }
503
    }
504

505
    void addLabelToIssue(String owner, String name, GhIssue issue, GhLabel label) {
506
        context.getLogger().debug(RB.$("git.issue.label", label.getName(), issue.getNumber()));
×
507

508
        Map<String, List<String>> labels = new LinkedHashMap<>();
×
509
        List<String> list = labels.computeIfAbsent("labels", k -> new ArrayList<>());
×
510
        list.addAll(issue.getLabels().stream().map(GhLabel::getName).collect(toList()));
×
511
        list.add(label.getName());
×
512

513
        api.labelIssue(labels, owner, name, issue.getNumber());
×
514
    }
×
515

516
    void commentOnIssue(String owner, String name, GhIssue issue, String comment) {
517
        context.getLogger().debug(RB.$("git.issue.comment", issue.getNumber()));
×
518

519
        Map<String, String> params = new LinkedHashMap<>();
×
520
        params.put("body", comment);
×
521

522
        api.commentIssue(params, owner, name, issue.getNumber());
×
523
    }
×
524

525
    void setMilestoneOnIssue(String owner, String name, GhIssue issue, GhMilestone milestone) {
526
        Map<String, Object> params = new LinkedHashMap<>();
×
527
        params.put("milestone", milestone.getNumber());
×
528

529
        api.updateIssue(params, owner, name, issue.getNumber());
×
530
    }
×
531

532
    private List<GhLabel> listLabels(String owner, String repoName) {
533
        context.getLogger().debug(RB.$("git.list.labels"), owner, repoName);
×
534

535
        List<GhLabel> labels = new ArrayList<>();
×
536

537
        int pageCount = 0;
×
538
        Map<String, Object> params = new LinkedHashMap<>();
×
539
        params.put("limit", 20);
×
540

541
        boolean consume = true;
×
542
        do {
543
            params.put("page", ++pageCount);
×
544
            Page<List<GhLabel>> page = api.listLabels(owner, repoName, params);
×
545
            labels.addAll(page.getContent());
×
546

547
            if (!page.hasLinks() || !page.getLinks().hasNext()) {
×
548
                consume = false;
×
549
            }
550
        }
551
        while (consume);
×
552

553
        return labels;
×
554
    }
555

556
    private GhOrganization resolveOrganization(String name) {
557
        try {
UNCOV
558
            return api.getOrganization(name);
×
559
        } catch (RestAPIException e) {
×
560
            if (e.isNotFound()) {
×
561
                // ok
562
                return null;
×
563
            }
564
            throw e;
×
565
        }
566
    }
567

568
    void updateRelease(String owner, String repo, String tag, Long id, GhRelease release) throws RestAPIException {
569
        context.getLogger().debug(RB.$("git.update.release"), owner, repo, tag);
×
570

571
        api.updateRelease(release, owner, repo, id);
×
572
    }
×
573

574
    private String getPrivateEmailUserId(String email) {
575
        if (!email.endsWith(USERS_NOREPLY_GITHUB_COM)) return null;
1✔
576
        String username = email.substring(0, email.indexOf("@"));
1✔
577
        if (username.contains("+")) {
1✔
578
            username = username.substring(username.indexOf("+") + 1);
1✔
579
        }
580
        return username;
1✔
581
    }
582

583
    Optional<User> findUser(String email, String name) throws RestAPIException {
584
        context.getLogger().debug(RB.$("git.user.lookup"), name, email);
1✔
585

586
        String username = getPrivateEmailUserId(email);
1✔
587
        if (null != username) {
1✔
588
            GhUser user = api.getUser(username);
1✔
589
            if (null != user) {
1✔
590
                return Optional.of(new User(user.getLogin(), email, user.getHtmlUrl()));
1✔
591
            }
592
        }
593

594
        GhSearchUser search = api.searchUser(CollectionUtils.<String, String>mapOf("q", email));
1✔
595
        if (search.getTotalCount() > 0) {
1✔
596
            GhUser user = search.getItems().get(0);
1✔
597
            return Optional.of(new User(user.getLogin(), email, user.getHtmlUrl()));
1✔
598
        }
599

600
        return Optional.empty();
1✔
601
    }
602

603
    GhReleaseNotes generateReleaseNotes(String owner, String repo, GhReleaseNotesParams params) throws RestAPIException {
604
        context.getLogger().info(RB.$("github.generate.release.notes"), owner, repo, params.getPreviousTagName(), params.getTagName());
×
605

606
        return api.generateReleaseNotes(params, owner, repo);
×
607
    }
608

609
    List<GhPackageVersion> listPackageVersions(String packageType, String packageName) throws IOException {
610
        context.getLogger().debug(RB.$("github.list.versions"), packageType, packageName);
1✔
611

612
        List<GhPackageVersion> issues = new ArrayList<>();
1✔
613
        Page<List<GhPackageVersion>> page = api.listPackageVersions0(packageType, packageName);
×
614
        issues.addAll(page.getContent());
×
615

616
        if (page.hasLinks() && page.getLinks().hasNext()) {
×
617
            try {
618
                collectPackageVersions(page, issues);
×
619
            } catch (URISyntaxException e) {
×
620
                throw new IOException(e);
×
621
            }
×
622
        }
623

624
        return issues;
×
625
    }
626

627
    private void collectPackageVersions(Page<List<GhPackageVersion>> page, List<GhPackageVersion> issues) throws URISyntaxException {
628
        URI next = new URI(page.getLinks().next());
×
629
        context.getLogger().debug(next.toString());
×
630

631
        page = api.listPackageVersions1(next);
×
632
        issues.addAll(page.getContent());
×
633

634
        if (page.hasLinks() && page.getLinks().hasNext()) {
×
635
            collectPackageVersions(page, issues);
×
636
        }
637
    }
×
638

639
    void deletePackageVersion(String packageType, String packageName, String packageVersion) throws RestAPIException {
640
        context.getLogger().debug(RB.$("github.delete.package.version"), packageVersion, packageName);
×
641

642
        api.deletePackageVersion(packageType, packageName, packageVersion);
×
643
    }
×
644

645
    void deletePackage(String packageType, String packageName) throws RestAPIException {
646
        context.getLogger().debug(RB.$("github.delete.package"), packageType, packageName);
×
647

648
        api.deletePackage(packageType, packageName);
×
649
    }
×
650
}
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