• 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

0.0
/sdks/jreleaser-codeberg-java-sdk/src/main/java/org/jreleaser/sdk/codeberg/CodebergReleaser.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.codeberg;
19

20
import org.jreleaser.bundle.RB;
21
import org.jreleaser.model.UpdateSection;
22
import org.jreleaser.model.api.common.Apply;
23
import org.jreleaser.model.api.common.ExtraProperties;
24
import org.jreleaser.model.internal.JReleaserContext;
25
import org.jreleaser.model.internal.util.VersionUtils;
26
import org.jreleaser.model.spi.release.ReleaseException;
27
import org.jreleaser.model.spi.release.User;
28
import org.jreleaser.mustache.TemplateContext;
29
import org.jreleaser.sdk.commons.RestAPIException;
30
import org.jreleaser.sdk.forgejo.Forgejo;
31
import org.jreleaser.sdk.forgejo.api.Asset;
32
import org.jreleaser.sdk.forgejo.api.Issue;
33
import org.jreleaser.sdk.forgejo.api.Label;
34
import org.jreleaser.sdk.forgejo.api.Milestone;
35
import org.jreleaser.sdk.forgejo.api.Release;
36
import org.jreleaser.sdk.forgejo.api.Repository;
37
import org.jreleaser.sdk.git.ChangelogProvider;
38
import org.jreleaser.sdk.git.GitSdk;
39
import org.jreleaser.sdk.git.release.AbstractReleaser;
40

41
import java.io.IOException;
42
import java.nio.file.Files;
43
import java.util.LinkedHashMap;
44
import java.util.List;
45
import java.util.Map;
46
import java.util.Optional;
47
import java.util.Set;
48
import java.util.TreeSet;
49
import java.util.regex.Pattern;
50

51
import static org.jreleaser.mustache.Templates.resolveTemplate;
52
import static org.jreleaser.util.StringUtils.capitalize;
53
import static org.jreleaser.util.StringUtils.uncapitalize;
54

55
/**
56
 * @author Andres Almiray
57
 * @since 0.4.0
58
 */
59
@org.jreleaser.infra.nativeimage.annotations.NativeImage
60
public class CodebergReleaser extends AbstractReleaser<org.jreleaser.model.api.release.CodebergReleaser> {
61
    private static final long serialVersionUID = -5294232312152902809L;
62

63
    private final org.jreleaser.model.internal.release.CodebergReleaser codeberg;
64

65
    public CodebergReleaser(JReleaserContext context, Set<org.jreleaser.model.spi.release.Asset> assets) {
66
        super(context, assets);
×
67
        codeberg = context.getModel().getRelease().getCodeberg();
×
68
    }
×
69

70
    @Override
71
    public org.jreleaser.model.api.release.CodebergReleaser getReleaser() {
72
        return codeberg.asImmutable();
×
73
    }
74

75
    @Override
76
    protected void createRelease() throws ReleaseException {
77
        String pullBranch = codeberg.getBranch();
×
NEW
78
        String pushBranch = codeberg.getResolvedBranchPush(context);
×
79
        boolean mustCheckoutBranch = !pushBranch.equals(pullBranch);
×
NEW
80
        context.getLogger().info(RB.$("git.releaser.releasing"), codeberg.getResolvedRepoUrl(context), pushBranch);
×
NEW
81
        String tagName = codeberg.getEffectiveTagName(context);
×
82

83
        try {
84
            Forgejo api = new Forgejo(context.asImmutable(),
×
85
                codeberg.getApiEndpoint(),
×
86
                codeberg.getToken(),
×
87
                codeberg.getConnectTimeout(),
×
88
                codeberg.getReadTimeout());
×
89

90
            if (!context.isDryrun()) {
×
91
                List<String> branchNames = api.listBranches(codeberg.getOwner(), codeberg.getName());
×
92
                GitSdk.of(context).checkoutBranch(codeberg, pushBranch, mustCheckoutBranch, !branchNames.contains(pushBranch));
×
93
            }
94

95
            String changelog = context.getChangelog().getResolvedChangelog();
×
96

97
            context.getLogger().debug(RB.$("git.releaser.release.lookup"), tagName, codeberg.getCanonicalRepoName());
×
98
            Release release = findReleaseByTag(api, tagName);
×
99
            boolean snapshot = context.getModel().getProject().isSnapshot();
×
100
            if (null != release) {
×
101
                context.getLogger().debug(RB.$("git.releaser.release.exists"), tagName);
×
102
                if (codeberg.isOverwrite() || snapshot) {
×
103
                    context.getLogger().debug(RB.$("git.releaser.release.delete"), tagName);
×
104
                    if (!context.isDryrun()) {
×
105
                        api.deleteRelease(codeberg.getOwner(), codeberg.getName(), tagName, release.getId());
×
106
                    }
107
                    context.getLogger().debug(RB.$("git.releaser.release.create"), tagName);
×
108
                    createRelease(api, tagName, changelog, snapshot && codeberg.isMatch());
×
109
                } else if (codeberg.getUpdate().isEnabled()) {
×
110
                    context.getLogger().debug(RB.$("git.releaser.release.update"), tagName);
×
111
                    if (!context.isDryrun()) {
×
112
                        Release updater = new Release();
×
113
                        if (codeberg.getPrerelease().isEnabledSet()) {
×
114
                            updater.setPrerelease(codeberg.getPrerelease().isEnabled());
×
115
                        }
116
                        if (codeberg.isDraftSet()) {
×
117
                            updater.setDraft(codeberg.isDraft());
×
118
                        }
119
                        if (codeberg.getUpdate().getSections().contains(UpdateSection.TITLE)) {
×
120
                            context.getLogger().info(RB.$("git.releaser.release.update.title"), codeberg.getEffectiveReleaseName());
×
121
                            updater.setName(codeberg.getEffectiveReleaseName());
×
122
                        }
123
                        if (codeberg.getUpdate().getSections().contains(UpdateSection.BODY)) {
×
124
                            context.getLogger().info(RB.$("git.releaser.release.update.body"));
×
125
                            updater.setBody(changelog);
×
126
                        }
127
                        api.updateRelease(codeberg.getOwner(), codeberg.getName(), release.getId(), updater);
×
128

129
                        if (codeberg.getUpdate().getSections().contains(UpdateSection.ASSETS)) {
×
130
                            updateAssets(api, release);
×
131
                        }
132
                        updateIssues(codeberg, api);
×
133
                    }
×
134
                } else {
135
                    if (context.isDryrun()) {
×
136
                        context.getLogger().debug(RB.$("git.releaser.release.create"), tagName);
×
137
                        createRelease(api, tagName, changelog, false);
×
138
                        return;
×
139
                    }
140

141
                    throw new IllegalStateException(RB.$("ERROR_git_releaser_cannot_release",
×
142
                        capitalize(codeberg.getServiceName()), tagName));
×
143
                }
144
            } else {
145
                context.getLogger().debug(RB.$("git.releaser.release.not.found"), tagName);
×
146
                context.getLogger().debug(RB.$("git.releaser.release.create"), tagName);
×
147
                createRelease(api, tagName, changelog, snapshot && codeberg.isMatch());
×
148
            }
149
        } catch (RestAPIException | IOException | IllegalStateException e) {
×
150
            context.getLogger().trace(e);
×
151
            throw new ReleaseException(e);
×
152
        }
×
153
    }
×
154

155
    private Release findReleaseByTag(Forgejo api, String tagName) {
156
        if (context.isDryrun()) return null;
×
157
        return api.findReleaseByTag(codeberg.getOwner(), codeberg.getName(), tagName);
×
158
    }
159

160
    protected org.jreleaser.model.spi.release.Repository.Kind resolveRepositoryKind() {
161
        return org.jreleaser.model.spi.release.Repository.Kind.CODEBERG;
×
162
    }
163

164
    @Override
165
    public org.jreleaser.model.spi.release.Repository maybeCreateRepository(String owner, String repo, String password, ExtraProperties extraProperties) throws IOException {
166
        context.getLogger().debug(RB.$("git.repository.lookup"), owner, repo);
×
167

168
        Forgejo api = new Forgejo(context.asImmutable(),
×
169
            codeberg.getApiEndpoint(),
×
170
            password,
171
            codeberg.getConnectTimeout(),
×
172
            codeberg.getReadTimeout());
×
173
        Repository repository = api.findRepository(owner, repo);
×
174
        if (null == repository) {
×
175
            repository = api.createRepository(owner, repo);
×
176
        }
177

178
        return new org.jreleaser.model.spi.release.Repository(
×
179
            resolveRepositoryKind(),
×
180
            owner,
181
            repo,
182
            repository.getHtmlUrl(),
×
183
            repository.getCloneUrl());
×
184
    }
185

186
    @Override
187
    public Optional<User> findUser(String email, String name) {
188
        try {
189
            String host = codeberg.getHost();
×
190
            String endpoint = codeberg.getApiEndpoint();
×
191
            if (endpoint.startsWith("https")) {
×
192
                host = "https://" + host;
×
193
            } else {
194
                host = "http://" + host;
×
195
            }
196
            if (!host.endsWith("/")) {
×
197
                host += "/";
×
198
            }
199

200
            return new Forgejo(context.asImmutable(),
×
201
                codeberg.getApiEndpoint(),
×
202
                codeberg.getToken(),
×
203
                codeberg.getConnectTimeout(),
×
204
                codeberg.getReadTimeout())
×
205
                .findUser(email, name, host);
×
206
        } catch (RestAPIException e) {
×
207
            context.getLogger().trace(e);
×
208
            context.getLogger().debug(RB.$("git.releaser.user.not.found"), email);
×
209
        }
210

211
        return Optional.empty();
×
212
    }
213

214
    @Override
215
    public List<org.jreleaser.model.spi.release.Release> listReleases(String owner, String repo) throws IOException {
216
        Forgejo api = new Forgejo(context.asImmutable(),
×
217
            codeberg.getApiEndpoint(),
×
218
            codeberg.getToken(),
×
219
            codeberg.getConnectTimeout(),
×
220
            codeberg.getReadTimeout());
×
221

222
        List<org.jreleaser.model.spi.release.Release> releases = api.listReleases(owner, repo);
×
223

224
        VersionUtils.clearUnparseableTags();
×
225
        Pattern versionPattern = VersionUtils.resolveVersionPattern(context);
×
226
        for (org.jreleaser.model.spi.release.Release release : releases) {
×
227
            release.setVersion(VersionUtils.version(context, release.getTagName(), versionPattern));
×
228
        }
×
229

230
        releases.sort((r1, r2) -> r2.getVersion().compareTo(r1.getVersion()));
×
231

232
        return releases;
×
233
    }
234

235
    private void createRelease(Forgejo api, String tagName, String changelog, boolean deleteTags) throws IOException {
236
        if (context.isDryrun()) {
×
237
            for (org.jreleaser.model.spi.release.Asset asset : assets) {
×
238
                if (0 == Files.size(asset.getPath()) || !Files.exists(asset.getPath())) {
×
239
                    // do not upload empty or non existent files
240
                    continue;
×
241
                }
242

243
                context.getLogger().info(" " + RB.$("git.upload.asset"), asset.getFilename());
×
244
            }
×
245
            updateIssues(codeberg, api);
×
246
            return;
×
247
        }
248

249
        if (deleteTags) {
×
250
            deleteTags(api, codeberg.getOwner(), codeberg.getName(), tagName);
×
251
        }
252

253
        // local tag
254
        if (deleteTags || !codeberg.isSkipTag()) {
×
255
            context.getLogger().debug(RB.$("git.releaser.repository.tag"), tagName, context.getModel().getCommit().getShortHash());
×
256
            GitSdk.of(context).tag(tagName, true, context);
×
257
        }
258

259
        // remote tag/release
260
        Release release = new Release();
×
261
        release.setName(codeberg.getEffectiveReleaseName());
×
262
        release.setTagName(tagName);
×
NEW
263
        release.setTargetCommitish(codeberg.getResolvedBranchPush(context));
×
264
        release.setBody(changelog);
×
265
        if (codeberg.getPrerelease().isEnabledSet()) {
×
266
            release.setPrerelease(codeberg.getPrerelease().isEnabled());
×
267
        }
268
        if (codeberg.isDraftSet()) {
×
269
            release.setDraft(codeberg.isDraft());
×
270
        }
271

272
        release = api.createRelease(codeberg.getOwner(), codeberg.getName(), release);
×
273
        api.uploadAssets(codeberg.getOwner(), codeberg.getName(), release, assets);
×
274

275
        if (codeberg.getMilestone().isClose() && !context.getModel().getProject().isSnapshot()) {
×
276
            Optional<Milestone> milestone = api.findMilestoneByName(
×
277
                codeberg.getOwner(),
×
278
                codeberg.getName(),
×
279
                codeberg.getMilestone().getEffectiveName());
×
280
            milestone.ifPresent(gtMilestone -> api.closeMilestone(codeberg.getOwner(),
×
281
                codeberg.getName(),
×
282
                gtMilestone));
283
        }
284

285
        updateIssues(codeberg, api);
×
286
    }
×
287

288
    private void updateIssues(org.jreleaser.model.internal.release.CodebergReleaser codeberg, Forgejo api) throws IOException {
289
        if (!codeberg.getIssues().isEnabled()) return;
×
290

291
        List<String> issueNumbers = ChangelogProvider.getIssues(context);
×
292

293
        if (!issueNumbers.isEmpty()) {
×
294
            context.getLogger().info(RB.$("git.issue.release.mark", issueNumbers.size()));
×
295
        }
296

297
        if (context.isDryrun()) {
×
298
            for (String issueNumber : issueNumbers) {
×
299
                context.getLogger().debug(RB.$("git.issue.release", issueNumber));
×
300
            }
×
301
            return;
×
302
        }
303

NEW
304
        String tagName = codeberg.getEffectiveTagName(context);
×
305
        String labelName = codeberg.getIssues().getLabel().getName();
×
306
        String labelColor = codeberg.getIssues().getLabel().getColor();
×
NEW
307
        TemplateContext props = codeberg.props(context);
×
NEW
308
        codeberg.fillProps(props, context);
×
NEW
309
        String comment = resolveTemplate(context.getLogger(), codeberg.getIssues().getComment(), props);
×
310

311
        if (labelColor.startsWith("#")) {
×
312
            labelColor = labelColor.substring(1);
×
313
        }
314

315
        Label gtLabel = null;
×
316

317
        try {
318
            gtLabel = api.getOrCreateLabel(
×
319
                codeberg.getOwner(),
×
320
                codeberg.getName(),
×
321
                labelName,
322
                labelColor,
323
                codeberg.getIssues().getLabel().getDescription());
×
324
        } catch (RestAPIException e) {
×
325
            throw new IllegalStateException(RB.$("ERROR_git_releaser_fetch_label", tagName, labelName), e);
×
326
        }
×
327

328
        Optional<Milestone> milestone = Optional.empty();
×
329
        Apply applyMilestone = codeberg.getIssues().getApplyMilestone();
×
330
        if (codeberg.getMilestone().isClose() && !context.getModel().getProject().isSnapshot()) {
×
331
            milestone = api.findMilestoneByName(
×
332
                codeberg.getOwner(),
×
333
                codeberg.getName(),
×
334
                codeberg.getMilestone().getEffectiveName());
×
335

336
            if (!milestone.isPresent()) {
×
337
                milestone = api.findClosedMilestoneByName(
×
338
                    codeberg.getOwner(),
×
339
                    codeberg.getName(),
×
340
                    codeberg.getMilestone().getEffectiveName());
×
341
            }
342
        }
343

344
        for (String issueNumber : issueNumbers) {
×
345
            Optional<Issue> op = api.findIssue(codeberg.getOwner(), codeberg.getName(), Integer.parseInt(issueNumber));
×
346
            if (!op.isPresent()) continue;
×
347

348
            Issue gtIssue = op.get();
×
349
            if ("closed".equals(gtIssue.getState()) && gtIssue.getLabels().stream().noneMatch(l -> l.getName().equals(labelName))) {
×
350
                context.getLogger().debug(RB.$("git.issue.release", issueNumber));
×
351

352
                try {
353
                    api.addLabelToIssue(codeberg.getOwner(), codeberg.getName(), gtIssue, gtLabel);
×
354
                    api.commentOnIssue(codeberg.getOwner(), codeberg.getName(), gtIssue, comment);
×
355

356
                    milestone.ifPresent(gtMilestone -> applyMilestone(codeberg, api, issueNumber, gtIssue, applyMilestone, gtMilestone));
×
357
                } catch (RestAPIException e) {
×
358
                    if (e.isForbidden()) {
×
359
                        context.getLogger().warn(e.getMessage());
×
360
                    } else {
361
                        throw e;
×
362
                    }
363
                }
×
364
            }
365
        }
×
366
    }
×
367

368
    private void applyMilestone(org.jreleaser.model.internal.release.CodebergReleaser codeberg, Forgejo api, String issueNumber, Issue gtIssue, Apply applyMilestone, Milestone targetMilestone) {
369
        Milestone issueMilestone = gtIssue.getMilestone();
×
370
        String targetMilestoneTitle = targetMilestone.getTitle();
×
371

372
        if (null == issueMilestone) {
×
373
            context.getLogger().debug(RB.$("git.issue.milestone.apply", targetMilestoneTitle, issueNumber));
×
374
            api.setMilestoneOnIssue(codeberg.getOwner(), codeberg.getName(), gtIssue, targetMilestone);
×
375
        } else {
376
            String milestoneTitle = issueMilestone.getTitle();
×
377

378
            if (applyMilestone == Apply.ALWAYS) {
×
379
                context.getLogger().debug(uncapitalize(RB.$("git.issue.milestone.warn", issueNumber, milestoneTitle)));
×
380
            } else if (applyMilestone == Apply.WARN) {
×
381
                if (!milestoneTitle.equals(targetMilestoneTitle)) {
×
382
                    context.getLogger().warn(RB.$("git.issue.milestone.warn", issueNumber, milestoneTitle));
×
383
                }
384
            } else if (applyMilestone == Apply.FORCE) {
×
385
                if (!milestoneTitle.equals(targetMilestoneTitle)) {
×
386
                    context.getLogger().warn(RB.$("git.issue.milestone.force", targetMilestoneTitle, issueNumber, milestoneTitle));
×
387
                    api.setMilestoneOnIssue(codeberg.getOwner(), codeberg.getName(), gtIssue, targetMilestone);
×
388
                } else {
389
                    context.getLogger().debug(uncapitalize(RB.$("git.issue.milestone.warn", issueNumber, milestoneTitle)));
×
390
                }
391
            }
392
        }
393
    }
×
394

395
    private void updateAssets(Forgejo api, Release release) throws IOException {
396
        Set<org.jreleaser.model.spi.release.Asset> assetsToBeUpdated = new TreeSet<>();
×
397
        Set<org.jreleaser.model.spi.release.Asset> assetsToBeUploaded = new TreeSet<>();
×
398

399
        Map<String, Asset> existingAssets = api.listAssets(codeberg.getOwner(), codeberg.getName(), release);
×
400
        Map<String, org.jreleaser.model.spi.release.Asset> assetsToBePublished = new LinkedHashMap<>();
×
401
        assets.forEach(asset -> assetsToBePublished.put(asset.getFilename(), asset));
×
402

403
        assetsToBePublished.keySet().forEach(name -> {
×
404
            if (existingAssets.containsKey(name)) {
×
405
                assetsToBeUpdated.add(assetsToBePublished.get(name));
×
406
            } else {
407
                assetsToBeUploaded.add(assetsToBePublished.get(name));
×
408
            }
409
        });
×
410

411
        api.updateAssets(codeberg.getOwner(), codeberg.getName(), release, assetsToBeUpdated, existingAssets);
×
412
        api.uploadAssets(codeberg.getOwner(), codeberg.getName(), release, assetsToBeUploaded);
×
413
    }
×
414

415
    private void deleteTags(Forgejo api, String owner, String repo, String tagName) {
416
        // delete remote tag
417
        try {
418
            api.deleteTag(owner, repo, tagName);
×
419
        } catch (RestAPIException ignored) {
×
420
            //noop
421
        }
×
422
    }
×
423
}
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