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

jreleaser / jreleaser / #475

03 Apr 2025 10:50AM UTC coverage: 40.322% (-8.9%) from 49.193%
#475

push

github

aalmiray
feat(release): Support Forgejo as releaser

Closes #1842

Closes #1843

182 of 1099 new or added lines in 45 files covered. (16.56%)

4239 existing lines in 333 files now uncovered.

20797 of 51577 relevant lines covered (40.32%)

0.4 hits per line

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

82.88
/api/jreleaser-model-api/src/main/java/org/jreleaser/version/SemanticVersion.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.version;
19

20
import org.jreleaser.bundle.RB;
21

22
import java.util.Objects;
23
import java.util.regex.Matcher;
24
import java.util.regex.Pattern;
25

26
import static org.jreleaser.util.ObjectUtils.requireState;
27
import static org.jreleaser.util.StringUtils.isNotBlank;
28
import static org.jreleaser.util.StringUtils.requireNonBlank;
29

30
/**
31
 * @author Andres Almiray
32
 * @since 0.2.0
33
 */
34
public class SemanticVersion implements Version<SemanticVersion> {
35
    private static final Pattern FULL_SEMVER_PATTERN = Pattern.compile("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:([\\.\\-])((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$");
1✔
36
    private static final Pattern MAJOR_MINOR_PATTERN = Pattern.compile("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:([\\.\\-])((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$");
1✔
37
    private static final Pattern MAJOR_PATTERN = Pattern.compile("^(0|[1-9]\\d*)(?:([\\.\\-])((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$");
1✔
38

39
    private final int major;
40
    private final int minor;
41
    private final int patch;
42
    private final String tagsep;
43
    private final String tag;
44
    private final String build;
45
    private final Pattern pattern;
46

47
    private SemanticVersion(int major, int minor, int patch, String tagsep, String tag, String build, Pattern pattern) {
1✔
48
        this.major = major;
1✔
49
        this.minor = minor;
1✔
50
        this.patch = patch;
1✔
51
        this.tagsep = isNotBlank(tagsep) ? tagsep.trim() : null;
1✔
52
        this.tag = isNotBlank(tag) ? tag.trim() : null;
1✔
53
        this.build = isNotBlank(build) ? build.trim() : null;
1✔
54
        this.pattern = pattern;
1✔
55

56
        if (isNotBlank(tagsep)) {
1✔
57
            requireState(".".equals(tagsep) || "-".equals(tagsep), "Argument 'tagsep' must not be '.' or '-'");
1✔
58
        }
59
    }
1✔
60

61
    public boolean hasMinor() {
62
        return minor != -1;
1✔
63
    }
64

65
    public boolean hasPatch() {
66
        return patch != -1;
1✔
67
    }
68

69
    public boolean hasTag() {
70
        return isNotBlank(tag);
1✔
71
    }
72

73
    public boolean hasBuild() {
74
        return isNotBlank(build);
1✔
75
    }
76

77
    public int getMajor() {
78
        return major;
1✔
79
    }
80

81
    public int getMinor() {
82
        return minor;
1✔
83
    }
84

85
    public int getPatch() {
86
        return patch;
1✔
87
    }
88

89
    public String getTagsep() {
90
        return tagsep;
×
91
    }
92

93
    public String getTag() {
94
        return tag;
1✔
95
    }
96

97
    public String getBuild() {
98
        return build;
1✔
99
    }
100

101
    @Override
102
    public String toRpmVersion() {
UNCOV
103
        StringBuilder b = new StringBuilder();
×
UNCOV
104
        b.append(major);
×
UNCOV
105
        if (hasMinor()) b.append(".").append(minor);
×
UNCOV
106
        if (hasPatch()) b.append(".").append(patch);
×
UNCOV
107
        if (hasTag()) b.append("~").append(tag.replace("-", "_"));
×
UNCOV
108
        if (hasBuild()) b.append("_").append(build.replace("-", "_"));
×
UNCOV
109
        return b.toString();
×
110
    }
111

112
    @Override
113
    public boolean equalsSpec(SemanticVersion version) {
114
        return pattern.pattern().equals(version.pattern.pattern());
×
115
    }
116

117
    @Override
118
    public String toString() {
119
        StringBuilder b = new StringBuilder();
1✔
120
        b.append(major);
1✔
121
        if (hasMinor()) b.append(".").append(minor);
1✔
122
        if (hasPatch()) b.append(".").append(patch);
1✔
123
        if (hasTag()) b.append(tagsep).append(tag);
1✔
124
        if (hasBuild()) b.append("+").append(build);
1✔
125
        return b.toString();
1✔
126
    }
127

128
    @Override
129
    public boolean equals(Object o) {
130
        if (this == o) return true;
1✔
131
        if (null == o || getClass() != o.getClass()) return false;
×
132
        SemanticVersion version = (SemanticVersion) o;
×
133
        return pattern.pattern().equals(version.pattern.pattern()) &&
×
134
            major == version.major &&
135
            minor == version.minor &&
136
            patch == version.patch &&
137
            Objects.equals(tag, version.tag) &&
×
138
            Objects.equals(build, version.build);
×
139
    }
140

141
    @Override
142
    public int hashCode() {
143
        return Objects.hash(pattern.pattern(), major, minor, patch, tag, build);
1✔
144
    }
145

146
    @Override
147
    public int compareTo(SemanticVersion other) {
148
        int result = major - other.major;
1✔
149
        if (result == 0) {
1✔
150
            result = minor - other.minor;
1✔
151
            if (result == 0) {
1✔
152
                result = patch - other.patch;
1✔
153
            }
154
        }
155

156
        if (result == 0 && isNotBlank(tag)) {
1✔
157
            result = isNotBlank(other.tag) ? tag.compareTo(other.tag) : -1;
1✔
158
        }
159
        if (result == 0 && isNotBlank(build)) {
1✔
160
            result = isNotBlank(other.build) ? build.compareTo(other.build) : -1;
1✔
161
        }
162

163
        return result;
1✔
164
    }
165

166
    public static int javaMajorVersion() {
167
        String jv = System.getProperty("java.version");
1✔
168
        if (jv.startsWith("1.")) {
1✔
169
            // this can only be Java 8
170
            return 8;
×
171
        }
172
        return JavaRuntimeVersion.of(jv).feature();
1✔
173
    }
174

175
    public static SemanticVersion defaultOf() {
176
        return of("0.0.0");
1✔
177
    }
178

179
    public static SemanticVersion of(String version) {
180
        requireNonBlank(version, "Argument 'version' must not be blank");
1✔
181

182
        Matcher m = FULL_SEMVER_PATTERN.matcher(version.trim());
1✔
183

184
        if (m.matches()) {
1✔
185
            String major = m.group(1);
1✔
186
            String minor = m.group(2);
1✔
187
            String patch = m.group(3);
1✔
188
            String tagsep = m.group(4);
1✔
189
            String tag = m.group(5);
1✔
190
            String build = m.group(6);
1✔
191

192
            return of(Integer.parseInt(major),
1✔
193
                Integer.parseInt(minor),
1✔
194
                Integer.parseInt(patch),
1✔
195
                isNotBlank(tagsep) ? tagsep : null,
1✔
196
                isNotBlank(tag) ? tag : null,
1✔
197
                isNotBlank(build) ? build : null);
1✔
198
        }
199

200
        m = MAJOR_MINOR_PATTERN.matcher(version);
1✔
201
        if (m.matches()) {
1✔
202
            String major = m.group(1);
1✔
203
            String minor = m.group(2);
1✔
204
            String tagsep = m.group(3);
1✔
205
            String tag = m.group(4);
1✔
206
            String build = m.group(5);
1✔
207

208
            return of(Integer.parseInt(major),
1✔
209
                Integer.parseInt(minor),
1✔
210
                isNotBlank(tagsep) ? tagsep : null,
1✔
211
                isNotBlank(tag) ? tag : null,
1✔
212
                isNotBlank(build) ? build : null);
1✔
213
        }
214

215
        m = MAJOR_PATTERN.matcher(version);
1✔
216
        if (m.matches()) {
1✔
217
            String major = m.group(1);
1✔
218
            String tagsep = m.group(2);
1✔
219
            String tag = m.group(3);
1✔
220
            String build = m.group(4);
1✔
221

222
            return of(Integer.parseInt(major),
1✔
223
                isNotBlank(tagsep) ? tagsep : null,
1✔
224
                isNotBlank(tag) ? tag : null,
1✔
225
                isNotBlank(build) ? build : null);
1✔
226
        }
227

228
        throw new IllegalArgumentException(RB.$("ERROR_version_parse", version));
×
229
    }
230

231
    public static SemanticVersion of(int major, int minor, int patch, String tag, String build) {
232
        return of(major, minor, patch, ".", tag, build);
×
233
    }
234

235
    public static SemanticVersion of(int major, int minor, int patch, String tagsep, String tag, String build) {
236
        requireState(major > -1, "Argument 'major' must not be negative");
1✔
237
        requireState(minor > -1, "Argument 'minor' must not be negative");
1✔
238
        requireState(patch > -1, "Argument 'patch' must not be negative");
1✔
239
        return new SemanticVersion(major, minor, patch, tagsep, tag, build, FULL_SEMVER_PATTERN);
1✔
240
    }
241

242
    public static SemanticVersion of(int major, int minor, String tag, String build) {
243
        return of(major, minor, ".", tag, build);
×
244
    }
245

246
    public static SemanticVersion of(int major, int minor, String tagsep, String tag, String build) {
247
        requireState(major > -1, "Argument 'major' must not be negative");
1✔
248
        requireState(minor > -1, "Argument 'minor' must not be negative");
1✔
249
        return new SemanticVersion(major, minor, -1, tagsep, tag, build, MAJOR_MINOR_PATTERN);
1✔
250
    }
251

252
    public static SemanticVersion of(int major, String tag, String build) {
253
        return of(major, ".", tag, build);
×
254
    }
255

256
    public static SemanticVersion of(int major, String tagsep, String tag, String build) {
257
        requireState(major > -1, "Argument 'major' must not be negative");
1✔
258
        return new SemanticVersion(major, -1, -1, tagsep, tag, build, MAJOR_PATTERN);
1✔
259
    }
260
}
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