├── .gitignore
├── README.md
├── pom.xml
├── release.md
└── src
├── it
├── settings.xml
├── test-multi-module
│ ├── README.md
│ ├── child1
│ │ ├── pom.xml
│ │ └── src
│ │ │ ├── main
│ │ │ └── java
│ │ │ │ └── de
│ │ │ │ └── jutzig
│ │ │ │ └── maven
│ │ │ │ └── test
│ │ │ │ └── HelloWorld.java
│ │ │ └── test
│ │ │ └── java
│ │ │ └── de
│ │ │ └── jutzig
│ │ │ └── maven
│ │ │ └── test
│ │ │ └── HelloWorldTest.java
│ ├── child2
│ │ ├── pom.xml
│ │ └── src
│ │ │ ├── main
│ │ │ └── java
│ │ │ │ └── de
│ │ │ │ └── jutzig
│ │ │ │ └── maven
│ │ │ │ └── test
│ │ │ │ └── HelloWorld.java
│ │ │ └── test
│ │ │ └── java
│ │ │ └── de
│ │ │ └── jutzig
│ │ │ └── maven
│ │ │ └── test
│ │ │ └── HelloWorldTest.java
│ ├── invoker.properties
│ ├── pom.xml
│ └── verify.groovy
└── test-release
│ ├── README.md
│ ├── invoker.properties
│ ├── pom.xml
│ ├── src
│ ├── main
│ │ └── java
│ │ │ └── de
│ │ │ └── jutzig
│ │ │ └── maven
│ │ │ └── test
│ │ │ └── HelloWorld.java
│ └── test
│ │ └── java
│ │ └── de
│ │ └── jutzig
│ │ └── maven
│ │ └── test
│ │ └── HelloWorldTest.java
│ └── verify.groovy
├── main
└── java
│ └── de
│ └── jutzig
│ └── github
│ └── release
│ └── plugin
│ └── UploadMojo.java
└── test
└── java
└── de
└── jutzig
└── github
└── release
└── plugin
└── UploadMojoTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Eclipse #
2 | /.classpath
3 | /.project
4 | /.settings/
5 |
6 | # Idea #
7 | /.idea
8 | /*.iml
9 |
10 | # Maven #
11 | /target/
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | github-release-plugin
2 | =====================
3 |
4 | [](https://maven-badges.herokuapp.com/maven-central/de.jutzig/github-release-plugin)
5 |
6 | uses the github release api to upload files
7 |
8 | To use the plugin you need to configure it in your `pom.xml` like so
9 |
10 | ```
11 |
12 | de.jutzig
13 | github-release-plugin
14 | 1.5.0
15 |
16 | Description of your release
17 | 1.0 Final
18 | ${project.version}
19 |
20 |
23 |
24 |
25 | ${project.build.directory}
26 |
27 | ${project.artifactId}*.tar.gz
28 | ${project.artifactId}*.zip
29 |
30 |
31 |
32 |
33 |
34 | ```
35 |
36 | Unless otherwise specified, the plugin will upload the main artifact of your project and take the github repository url from the `` section.
37 |
38 | By default, the plugin will look for your github credentials in your maven `settings.xml`. Credentials can be privated as an API token, or username and password. Example
39 | ```
40 |
41 |
42 | github
43 | API_TOKEN
44 | GITHUB_USERNAME
45 | GITHUB_PASSWORD
46 |
47 |
48 | ```
49 |
50 | These credentials can be overridden by setting `username` and `password` as system properties.
51 |
52 | Additional Parameters:
53 |
54 | * `-Dgithub.draft=true` creates the release in draft state
55 | * `-Dgithub.commitish=release/1.0.0` allows to specify a commitsh
56 | * `-Dgithub.apitoken=API_TOKEN` allows to set github api token instead of providing it in settings.xml
57 | * `-Dgithub.username=GITHUB_USERNAME` allows to set github username instead of providing it in settings.xml
58 | * `-Dgithub.password=GITHUB_PASSWORD` allows to set github password instead of providing it in settings.xml
59 |
60 | The plugin is available on Maven central
61 |
62 | ## Note on release description
63 | The easiest way of getting a good release description (aka release notes or changelog) is to defer its generation to the GitHub. So if you completely leave out
64 | the `` configuration parameter, then GitHub will generate a meaningful description for you. Otherwise, if description _is_ supplied, it will be
65 | prepended to one generated by GitHub.
66 |
67 | Below is an example of how the auto-generated release notes by GitHub could look like:
68 |
69 |
70 |
71 | You can read how to customize release notes format in [GitHub documentation][github-release-notes-format].
72 |
73 | ## Note on the GitHub API endpoints
74 | The endpoint for GitHub API is inferred from `` connection string. When missing, it by default would use the public endpoint at https://api.github.com.
75 | If you want to upload to a GitHub enterprise instance, then a respective `` connection string must be specified.
76 |
77 |
78 | [github-release-notes-format]: https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes#configuring-automatically-generated-release-notes
79 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 | de.jutzig
4 | github-release-plugin
5 | maven-plugin
6 | 1.6.1-SNAPSHOT
7 | github-release-plugin
8 | Maven plugin to create github releases and upload assets to the release
9 | https://github.com/jutzig/github-release-plugin
10 |
11 |
12 |
13 | The Apache License, Version 2.0
14 | http://www.apache.org/licenses/LICENSE-2.0.txt
15 |
16 |
17 |
18 |
19 |
20 | jutzig
21 | Johannes Utzig
22 | jutzig.dev@gmail.com
23 | https://github.com/jutzig
24 |
25 |
26 |
27 |
28 | https://github.com/jutzig/github-release-plugin
29 | scm:git:https://github.com/jutzig/github-release-plugin.git
30 | scm:git:https://github.com/jutzig/github-release-plugin.git
31 | HEAD
32 |
33 |
34 |
35 |
36 | ossrh
37 | https://oss.sonatype.org/content/repositories/snapshots
38 |
39 |
40 | ossrh
41 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
42 |
43 |
44 |
45 |
46 | 3.6.0
47 |
48 |
49 |
50 | UTF-8
51 | 1.8
52 | ${java.version}
53 | ${java.version}
54 |
55 |
56 |
57 |
58 |
59 | org.apache.maven.plugins
60 | maven-plugin-plugin
61 | 3.6.4
62 |
63 |
64 | org.apache.maven.plugins
65 | maven-javadoc-plugin
66 | 3.1.1
67 |
68 | -Xdoclint:none
69 |
70 |
71 |
72 | org.apache.maven.plugins
73 | maven-invoker-plugin
74 | 3.2.1
75 |
76 | false
77 | clean
78 | verify
79 | src/it/settings.xml
80 | ${project.build.directory}/it
81 | true
82 | true
83 |
84 |
85 |
86 | integration-tests-with-poms
87 |
88 | install
89 | run
90 |
91 |
92 | none
93 |
94 |
95 |
96 |
97 |
98 | org.codehaus.groovy
99 | groovy-all
100 | 2.4.5
101 |
102 |
103 |
104 |
105 |
106 | maven-release-plugin
107 | 2.5.3
108 |
109 | true
110 | false
111 | release
112 | deploy
113 |
114 |
115 |
116 | org.apache.maven.scm
117 | maven-scm-provider-gitexe
118 | 1.8.1
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 | release
128 |
129 |
130 |
131 | org.apache.maven.plugins
132 | maven-source-plugin
133 | 3.1.0
134 |
135 |
136 | attach-sources
137 |
138 | jar-no-fork
139 |
140 |
141 |
142 |
143 |
144 | org.apache.maven.plugins
145 | maven-javadoc-plugin
146 | 3.1.1
147 |
148 |
149 | attach-javadocs
150 |
151 | jar
152 |
153 |
154 |
155 |
156 |
157 | org.apache.maven.plugins
158 | maven-gpg-plugin
159 | 1.6
160 |
161 |
162 | sign-artifacts
163 | verify
164 |
165 | sign
166 |
167 |
168 |
169 |
170 |
171 | org.sonatype.plugins
172 | nexus-staging-maven-plugin
173 | 1.6.8
174 | true
175 |
176 | ossrh
177 | https://oss.sonatype.org/
178 | true
179 |
180 |
181 |
182 | de.jutzig
183 | github-release-plugin
184 | 1.6.0
185 |
186 |
187 | github-upload
188 | deploy
189 |
190 | release
191 |
192 | false
193 |
194 | v${project.version}
195 | v${project.version}
196 | ${project.build.directory}/${project.artifactId}-${project.version}.jar
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 | org.kohsuke
209 | github-api
210 | 1.318
211 |
212 |
213 |
214 | org.apache.maven
215 | maven-plugin-api
216 | 3.6.2
217 | provided
218 |
219 |
220 | org.apache.maven.plugin-tools
221 | maven-plugin-annotations
222 | 3.6.0
223 | provided
224 |
225 |
226 | org.apache.maven
227 | maven-core
228 | 3.5.4
229 | provided
230 |
231 |
232 | org.codehaus.plexus
233 | plexus-utils
234 | 3.2.1
235 |
236 |
237 |
238 | org.junit.jupiter
239 | junit-jupiter
240 | 5.10.1
241 | test
242 |
243 |
244 |
245 |
--------------------------------------------------------------------------------
/release.md:
--------------------------------------------------------------------------------
1 | Release:
2 |
3 | * see http://central.sonatype.org/pages/apache-maven.html
4 | * mvn versions:set -DnewVersion=1.2.3
5 | * mvn clean deploy -P release
--------------------------------------------------------------------------------
/src/it/settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | it-repo
7 |
8 | true
9 |
10 |
11 |
12 | local.central
13 | @localRepositoryUrl@
14 |
15 | true
16 |
17 |
18 | true
19 |
20 |
21 |
22 |
23 |
24 | local.central
25 | @localRepositoryUrl@
26 |
27 | true
28 |
29 |
30 | true
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/it/test-multi-module/README.md:
--------------------------------------------------------------------------------
1 | # How this works
2 |
3 | * The `maven-invoker-plugin` uses it's own `settings.xml` which is merged with the one from `~/.m2/settings.xml`.
4 | * The credentials for deploying too Github are under the `~/.m2/settings.xml`, so as to not having to add them
5 | as sources.
6 | * The `repositoryId` and expected `serverId` in the `~/.m2/settings.xml` file are as follows
7 | ```
8 | jutzig/github-release-plugin
9 | jutzig/github-release-plugin
10 |
11 | ```
12 |
--------------------------------------------------------------------------------
/src/it/test-multi-module/child1/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | de.jutzig
7 | test-parent
8 | @project.version@
9 |
10 | child1
11 |
12 |
13 |
14 |
15 | src/test/resources
16 | true
17 |
18 |
19 |
20 |
21 |
22 | @project.groupId@
23 | @project.artifactId@
24 | @project.version@
25 |
26 |
27 | Test artifact ${project.version}.
28 | ${project.version}
29 | ${project.version}
30 |
31 | true
32 | jutzig/github-release-plugin
33 | false
34 |
35 |
36 |
37 | ${project.build.directory}
38 |
39 | ${project.artifactId}*.jar
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | package
48 |
49 | release
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/it/test-multi-module/child1/src/main/java/de/jutzig/maven/test/HelloWorld.java:
--------------------------------------------------------------------------------
1 | package de.jutzig.maven.test;
2 |
3 | public class HelloWorld
4 | {
5 |
6 | public static void main(String[] args)
7 | {
8 | System.out.println("Hello, world!");
9 | }
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/it/test-multi-module/child1/src/test/java/de/jutzig/maven/test/HelloWorldTest.java:
--------------------------------------------------------------------------------
1 | package de.jutzig.maven.test;
2 |
3 | import java.io.IOException;
4 | import java.util.Properties;
5 |
6 | import org.junit.Assert;
7 | import org.junit.Test;
8 |
9 | public class HelloWorldTest
10 | {
11 |
12 | @Test
13 | public void connectionTest() throws IOException
14 | {
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/it/test-multi-module/child2/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | de.jutzig
7 | test-parent
8 | @project.version@
9 |
10 | child2
11 |
12 |
13 |
14 |
15 | src/test/resources
16 | true
17 |
18 |
19 |
20 |
21 |
22 | @project.groupId@
23 | @project.artifactId@
24 | @project.version@
25 |
26 |
27 | Test artifact ${project.version}.
28 | ${project.version}
29 | ${project.version}
30 |
31 | false
32 | jutzig/github-release-plugin
33 | false
34 |
35 |
36 |
37 | ${project.build.directory}
38 |
39 | ${project.artifactId}*.jar
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | package
48 |
49 | release
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/it/test-multi-module/child2/src/main/java/de/jutzig/maven/test/HelloWorld.java:
--------------------------------------------------------------------------------
1 | package de.jutzig.maven.test;
2 |
3 | public class HelloWorld
4 | {
5 |
6 | public static void main(String[] args)
7 | {
8 | System.out.println("Hello, world!");
9 | }
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/it/test-multi-module/child2/src/test/java/de/jutzig/maven/test/HelloWorldTest.java:
--------------------------------------------------------------------------------
1 | package de.jutzig.maven.test;
2 |
3 | import java.io.IOException;
4 | import java.util.Properties;
5 |
6 | import org.junit.Assert;
7 | import org.junit.Test;
8 |
9 | public class HelloWorldTest
10 | {
11 |
12 | @Test
13 | public void connectionTest() throws IOException
14 | {
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/it/test-multi-module/invoker.properties:
--------------------------------------------------------------------------------
1 | invoker.goals=deploy -Dmaven.deploy.skip=true -e -X
2 |
--------------------------------------------------------------------------------
/src/it/test-multi-module/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | de.jutzig
6 | test-parent
7 | @project.version@
8 | pom
9 |
10 |
11 | scm:git:git@github.com:jutzig/github-release-plugin.git
12 | scm:git:git@github.com:jutzig/github-release-plugin.git
13 | https://github.com/jutzig/github-release-plugin/tree/${project.scm.tag}
14 | master
15 |
16 |
17 |
18 | 1.8
19 | 1.8
20 |
21 |
22 |
23 |
24 | child1
25 | child2
26 |
27 |
28 |
29 |
30 | junit
31 | junit
32 | 4.11
33 | test
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/it/test-multi-module/verify.groovy:
--------------------------------------------------------------------------------
1 | // TODO: Add a proper check, but, basically, if the invoker build fails, that would be enough of a failure.
2 | return true
3 |
--------------------------------------------------------------------------------
/src/it/test-release/README.md:
--------------------------------------------------------------------------------
1 | # How this works
2 |
3 | * The `maven-invoker-plugin` uses it's own `settings.xml` which is merged with the one from `~/.m2/settings.xml`.
4 | * The credentials for deploying too Github are under the `~/.m2/settings.xml`, so as to not having to add them
5 | as sources.
6 | * The `repositoryId` and expected `serverId` in the `~/.m2/settings.xml` file are as follows
7 | ```
8 | jutzig/github-release-plugin
9 | jutzig/github-release-plugin
10 |
11 | ```
12 |
--------------------------------------------------------------------------------
/src/it/test-release/invoker.properties:
--------------------------------------------------------------------------------
1 | invoker.goals=deploy -Dmaven.deploy.skip=true -e -X
2 |
--------------------------------------------------------------------------------
/src/it/test-release/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | de.jutzig
6 | test-project
7 | @project.version@
8 |
9 |
10 |
11 | junit
12 | junit
13 | 4.11
14 | test
15 |
16 |
17 |
18 |
19 | scm:git:git@github.com:jutzig/github-release-plugin.git
20 | scm:git:git@github.com:jutzig/github-release-plugin.git
21 | https://github.com/jutzig/github-release-plugin/tree/${project.scm.tag}
22 | master
23 |
24 |
25 |
26 | 1.8
27 | 1.8
28 |
29 |
30 |
31 |
32 |
33 | src/test/resources
34 | true
35 |
36 |
37 |
38 |
39 |
40 | @project.groupId@
41 | @project.artifactId@
42 | @project.version@
43 |
44 |
45 | Test artifact ${project.version}.
46 | ${project.version}
47 | ${project.version}
48 |
49 | true
50 | jutzig/github-release-plugin
51 | jutzig/github-release-plugin
52 | true
53 |
54 |
55 |
56 | ${project.build.directory}
57 |
58 | ${project.artifactId}*.jar
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | package
67 |
68 | release
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/src/it/test-release/src/main/java/de/jutzig/maven/test/HelloWorld.java:
--------------------------------------------------------------------------------
1 | package de.jutzig.maven.test;
2 |
3 | public class HelloWorld
4 | {
5 |
6 | public static void main(String[] args)
7 | {
8 | System.out.println("Hello, world!");
9 | }
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/src/it/test-release/src/test/java/de/jutzig/maven/test/HelloWorldTest.java:
--------------------------------------------------------------------------------
1 | package de.jutzig.maven.test;
2 |
3 | import java.io.IOException;
4 | import java.util.Properties;
5 |
6 | import org.junit.Assert;
7 | import org.junit.Test;
8 |
9 | public class HelloWorldTest
10 | {
11 |
12 | @Test
13 | public void connectionTest() throws IOException
14 | {
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/it/test-release/verify.groovy:
--------------------------------------------------------------------------------
1 | // TODO: Add a proper check, but, basically, if the invoker build fails, that would be enough of a failure.
2 | return true
3 |
--------------------------------------------------------------------------------
/src/main/java/de/jutzig/github/release/plugin/UploadMojo.java:
--------------------------------------------------------------------------------
1 | package de.jutzig.github.release.plugin;
2 |
3 | /*
4 | * Copyright 2001-2005 The Apache Software Foundation.
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 | * http://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 |
19 | import java.io.File;
20 | import java.io.IOException;
21 | import java.text.MessageFormat;
22 | import java.util.List;
23 | import java.util.regex.Matcher;
24 | import java.util.regex.Pattern;
25 |
26 | import org.apache.commons.lang3.StringUtils;
27 | import org.apache.maven.model.FileSet;
28 | import org.apache.maven.model.Scm;
29 | import org.apache.maven.plugin.AbstractMojo;
30 | import org.apache.maven.plugin.MojoExecutionException;
31 | import org.apache.maven.plugins.annotations.Component;
32 | import org.apache.maven.plugins.annotations.LifecyclePhase;
33 | import org.apache.maven.plugins.annotations.Mojo;
34 | import org.apache.maven.plugins.annotations.Parameter;
35 | import org.apache.maven.project.MavenProject;
36 | import org.apache.maven.settings.Server;
37 | import org.apache.maven.settings.Settings;
38 | import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
39 | import org.apache.maven.settings.crypto.SettingsDecrypter;
40 | import org.apache.maven.settings.crypto.SettingsDecryptionResult;
41 | import org.codehaus.plexus.PlexusConstants;
42 | import org.codehaus.plexus.PlexusContainer;
43 | import org.codehaus.plexus.component.annotations.Requirement;
44 | import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
45 | import org.codehaus.plexus.context.Context;
46 | import org.codehaus.plexus.context.ContextException;
47 | import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
48 | import org.codehaus.plexus.util.FileUtils;
49 | import org.kohsuke.github.GHAsset;
50 | import org.kohsuke.github.GHRelease;
51 | import org.kohsuke.github.GHReleaseBuilder;
52 | import org.kohsuke.github.GHRepository;
53 | import org.kohsuke.github.GitHub;
54 | import org.kohsuke.github.GitHubBuilder;
55 | import org.kohsuke.github.PagedIterable;
56 |
57 | /**
58 | * Goal which attaches a file to a GitHub release
59 | */
60 | @Mojo(name = "release", defaultPhase = LifecyclePhase.DEPLOY)
61 | public class UploadMojo extends AbstractMojo implements Contextualizable{
62 |
63 | private static final String PUBLIC_GITUHB_API_ENDPOINT = "https://api.github.com";
64 | /**
65 | * Server id for github access.
66 | */
67 | @Parameter(defaultValue = "github")
68 | private String serverId;
69 |
70 | /**
71 | * The tag name this release is based on.
72 | */
73 | @Parameter(defaultValue = "${project.version}")
74 | private String tag;
75 |
76 | /**
77 | * The name of the release
78 | */
79 | @Parameter(property = "relase.name")
80 | private String releaseName;
81 |
82 | /**
83 | * The release description
84 | */
85 | @Parameter
86 | private String description;
87 |
88 | /**
89 | * The commitish to use
90 | */
91 | @Parameter(property = "github.commitish")
92 | private String commitish;
93 |
94 | /**
95 | * Whether or not the release should be draft
96 | */
97 | @Parameter(property = "github.draft")
98 | private Boolean draft;
99 |
100 | /**
101 | * The github id of the project. By default initialized from the project scm connection
102 | */
103 | @Parameter(property = "release.repositoryId", defaultValue = "${project.scm.connection}", required = true)
104 | private String repositoryId;
105 |
106 | /**
107 | * The Maven settings
108 | */
109 | @Parameter(defaultValue = "${settings}", readonly = true, required = true)
110 | private Settings settings;
111 |
112 | /**
113 | * The file to upload to the release. Default is ${project.build.directory}/${project.artifactId}-${project.version}.${project.packaging} (the main artifact)
114 | */
115 | @Parameter(property = "release.artifact", defaultValue = "${project.build.directory}/${project.artifactId}-${project.version}.${project.packaging}")
116 | private String artifact;
117 |
118 | /**
119 | * A specific fileSet
rule to select files and directories for upload to the release.
120 | */
121 | @Parameter
122 | private FileSet fileSet;
123 |
124 | /**
125 | * A list of fileSet
rules to select files and directories for upload to the release.
126 | */
127 | @Parameter
128 | private List fileSets;
129 |
130 | /**
131 | * Flag to indicate to overwrite the asset in the release if it already exists. Default is false
132 | */
133 | @Parameter(defaultValue = "false")
134 | private Boolean overwriteArtifact;
135 |
136 | /**
137 | * Flag to indicate whether to remove releases, if they exist, before re-creating them. Default is false
138 | */
139 | @Parameter(defaultValue = "false")
140 | private Boolean deleteRelease;
141 |
142 | @Requirement
143 | private PlexusContainer container;
144 |
145 | /**
146 | * If this is a prerelease. Will be set by default according to ${project.version} (see {@link #guessPreRelease(String)}.
147 | */
148 | @Parameter
149 | private Boolean prerelease;
150 |
151 | /**
152 | * Fail plugin execution if release already exists.
153 | */
154 | @Parameter(defaultValue = "false")
155 | private Boolean failOnExistingRelease;
156 |
157 | @Component
158 | private MavenProject project;
159 |
160 | public void execute() throws MojoExecutionException {
161 | if(releaseName==null)
162 | releaseName = tag;
163 | if(prerelease==null)
164 | prerelease = guessPreRelease(tag);
165 | repositoryId = computeRepositoryId(repositoryId);
166 | GHRelease release = null;
167 | try {
168 | GitHub gitHub = createGithub(serverId);
169 | GHRepository repository = gitHub.getRepository(repositoryId);
170 | release = findRelease(repository,releaseName);
171 | if (release != null) {
172 | String message = "Release " + releaseName + " already exists. Not creating";
173 |
174 | if (failOnExistingRelease) {
175 | throw new MojoExecutionException(message);
176 | }
177 |
178 | if (deleteRelease) {
179 | getLog().info("Removing existing release " + release.getName() + "...");
180 |
181 | release.delete();
182 |
183 | getLog().info("Release " + release.getName() + " removed successfully.");
184 | release = null;
185 | } else {
186 | getLog().info(message);
187 | }
188 | }
189 | if (release == null) {
190 | getLog().info("Creating release "+releaseName);
191 | GHReleaseBuilder builder = repository.createRelease(tag);
192 | builder.generateReleaseNotes(true);
193 | if(description!=null) {
194 | builder.body(description);
195 | }
196 | if (commitish!=null) {
197 | builder.commitish(commitish);
198 | }
199 | if (draft!=null) {
200 | builder.draft(draft);
201 | }
202 |
203 | builder.prerelease(prerelease);
204 | builder.name(releaseName);
205 | release = builder.create();
206 | }
207 | } catch (IOException e) {
208 | getLog().error(e);
209 | throw new MojoExecutionException("Failed to create release", e);
210 | }
211 |
212 | try {
213 | if(artifact != null && !artifact.trim().isEmpty()) {
214 | File asset = new File(artifact);
215 | if(asset.exists()) {
216 | uploadAsset(release, asset);
217 | }
218 | }
219 |
220 | if(fileSet != null)
221 | uploadAssets(release, fileSet);
222 |
223 | if(fileSets != null)
224 | for (FileSet set : fileSets)
225 | uploadAssets(release, set);
226 |
227 | } catch (IOException e) {
228 |
229 | getLog().error(e);
230 | throw new MojoExecutionException("Failed to upload assets", e);
231 | }
232 | }
233 |
234 | private void uploadAsset(GHRelease release, File asset) throws IOException {
235 | getLog().info("Processing asset "+asset.getPath());
236 |
237 | List existingAssets = release.getAssets();
238 | for ( GHAsset a : existingAssets ){
239 | if (a.getName().equals( asset.getName() )){
240 | if(overwriteArtifact) {
241 | getLog().info(" Deleting existing asset");
242 | a.delete();
243 | }
244 | else
245 | {
246 | getLog().warn("Asset "+asset.getName()+" already exists. Skipping");
247 | return;
248 | }
249 | }
250 | }
251 |
252 | getLog().info(" Upload asset");
253 | // for some reason this doesn't work currently
254 | release.uploadAsset(asset, "application/zip");
255 | }
256 |
257 | private void uploadAssets(GHRelease release, FileSet fileset) throws IOException {
258 | List assets = FileUtils.getFiles(
259 | new File(fileset.getDirectory()),
260 | StringUtils.join(fileset.getIncludes(), ','),
261 | StringUtils.join(fileset.getExcludes(), ',')
262 | );
263 | for (File asset : assets)
264 | uploadAsset(release, asset);
265 | }
266 |
267 | private GHRelease findRelease(GHRepository repository, String releaseNameToFind) throws IOException {
268 | PagedIterable releases = repository.listReleases();
269 | for (GHRelease ghRelease : releases) {
270 | if (releaseNameToFind.equals(ghRelease.getName())) {
271 | return ghRelease;
272 | }
273 | }
274 | return null;
275 | }
276 |
277 | /**
278 | * @see SCM URL Format
279 | */
280 | private static final Pattern REPOSITORY_PATTERN = Pattern.compile(
281 | "^(scm:git[:|])?" + //Maven prefix for git SCM
282 | "(https?://[\\w\\d.-]+/|git@[\\w\\d.-]+:)" + //GitHub prefix for HTTP/HTTPS/SSH/Subversion scheme
283 | "([^/]+/[^/\\.]+)" + //Repository ID
284 | "(\\.git)?" + //Optional suffix ".git"
285 | "(/.*)?$" //Optional child project path
286 | , Pattern.CASE_INSENSITIVE);
287 |
288 | public static String computeRepositoryId(String id) {
289 | Matcher matcher = REPOSITORY_PATTERN.matcher(id);
290 | if (matcher.matches()) {
291 | return matcher.group(3);
292 | } else {
293 | return id;
294 | }
295 | }
296 |
297 | static String computeGithubApiEndpoint(Scm scm) {
298 | if (scm == null || StringUtils.isEmpty(scm.getConnection())) {
299 | return PUBLIC_GITUHB_API_ENDPOINT;
300 | }
301 | Matcher matcher = REPOSITORY_PATTERN.matcher(scm.getConnection());
302 | if (!matcher.matches()) {
303 | return PUBLIC_GITUHB_API_ENDPOINT;
304 | }
305 | String githubApiEndpoint = matcher.group(2);
306 | if (githubApiEndpoint.contains("github.com")) {
307 | return PUBLIC_GITUHB_API_ENDPOINT;
308 | }
309 |
310 | if (githubApiEndpoint.startsWith("git@")) {
311 | // According to the regex pattern above, the matched group would be in a form of git@hostname:
312 | githubApiEndpoint = githubApiEndpoint.substring(4, githubApiEndpoint.length() - 1);
313 | }
314 |
315 | githubApiEndpoint = StringUtils.removeEnd(githubApiEndpoint, "/");
316 | if (!githubApiEndpoint.startsWith("http")) {
317 | githubApiEndpoint = "https://" + githubApiEndpoint;
318 | }
319 | // See https://docs.github.com/en/enterprise-server@3.10/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#schema
320 | return githubApiEndpoint + "/api/v3";
321 | }
322 |
323 | public GitHub createGithub(String serverId) throws MojoExecutionException, IOException {
324 | String usernameProperty = System.getProperty("username");
325 | String passwordProperty = System.getProperty("password");
326 | String githubApiEndpoint = computeGithubApiEndpoint(project.getScm());
327 | GitHubBuilder gitHubBuilder = new GitHubBuilder().withEndpoint(githubApiEndpoint);
328 | if(usernameProperty!=null && passwordProperty!=null)
329 | {
330 | getLog().debug("Using server credentials from system properties 'username' and 'password'");
331 | return gitHubBuilder.withPassword(usernameProperty, passwordProperty).build();
332 | }
333 |
334 | Server server = getServer(settings, serverId);
335 | if (server == null)
336 | throw new MojoExecutionException(MessageFormat.format("Server ''{0}'' not found in settings", serverId));
337 |
338 | getLog().debug(MessageFormat.format("Using ''{0}'' server credentials", serverId));
339 |
340 | try {
341 | SettingsDecrypter settingsDecrypter = container.lookup(SettingsDecrypter.class);
342 | SettingsDecryptionResult result = settingsDecrypter.decrypt(new DefaultSettingsDecryptionRequest(server));
343 | server = result.getServer();
344 | } catch (ComponentLookupException cle) {
345 | throw new MojoExecutionException("Unable to lookup SettingsDecrypter: " + cle.getMessage(), cle);
346 | }
347 |
348 | String serverUsername = System.getProperty("github.username", server.getUsername());
349 | String serverPassword = System.getProperty("github.password", server.getPassword());
350 | String serverAccessToken = System.getProperty("github.apitoken", server.getPrivateKey());
351 | if (StringUtils.isNotEmpty(serverUsername) && StringUtils.isNotEmpty(serverPassword))
352 | return gitHubBuilder.withPassword(serverUsername, serverPassword).build();
353 | else if (StringUtils.isNotEmpty(serverAccessToken))
354 | return gitHubBuilder.withOAuthToken(serverAccessToken).build();
355 | else
356 | throw new MojoExecutionException("Configuration for server " + serverId + " has no login credentials");
357 | }
358 |
359 | /**
360 | * Get server with given id
361 | *
362 | * @param settings
363 | * @param serverId
364 | * must be non-null and non-empty
365 | * @return server or null if none matching
366 | */
367 | protected Server getServer(final Settings settings, final String serverId) {
368 | if (settings == null)
369 | return null;
370 | List servers = settings.getServers();
371 | if (servers == null || servers.isEmpty())
372 | return null;
373 |
374 | for (Server server : servers)
375 | if (serverId.equals(server.getId()))
376 | return server;
377 | return null;
378 | }
379 |
380 | public void contextualize(Context context) throws ContextException {
381 | container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY );
382 | }
383 |
384 | /**
385 | * Guess if a version defined in POM should be considered as {@link #prerelease}.
386 | */
387 | static boolean guessPreRelease(String version) {
388 | boolean preRelease = version.endsWith("-SNAPSHOT")
389 | || StringUtils.containsIgnoreCase(version, "-alpha")
390 | || StringUtils.containsIgnoreCase(version, "-beta")
391 | || StringUtils.containsIgnoreCase(version, "-RC")
392 | || StringUtils.containsIgnoreCase(version, ".RC")
393 | || StringUtils.containsIgnoreCase(version, ".M")
394 | || StringUtils.containsIgnoreCase(version, ".BUILD_SNAPSHOT");
395 | return preRelease;
396 | }
397 | }
398 |
--------------------------------------------------------------------------------
/src/test/java/de/jutzig/github/release/plugin/UploadMojoTest.java:
--------------------------------------------------------------------------------
1 | package de.jutzig.github.release.plugin;
2 |
3 | import java.util.stream.Stream;
4 |
5 | import org.apache.maven.model.Scm;
6 | import org.junit.jupiter.api.Test;
7 | import org.junit.jupiter.params.ParameterizedTest;
8 | import org.junit.jupiter.params.provider.*;
9 |
10 | import static org.junit.jupiter.api.Assertions.*;
11 |
12 | class UploadMojoTest {
13 | @ParameterizedTest(name = "{0} should resolve to {1} repository id")
14 | @CsvSource({
15 | // Public
16 | "scm:git:https://github.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin",
17 | "scm:git|https://github.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin",
18 | "https://github.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin",
19 |
20 | "scm:git:http://github.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin",
21 | "scm:git|http://github.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin",
22 | "http://github.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin",
23 |
24 | "scm:git:git@github.com:jutzig/github-release-plugin.git, jutzig/github-release-plugin",
25 | "scm:git|git@github.com:jutzig/github-release-plugin.git, jutzig/github-release-plugin",
26 | "git@github.com:jutzig/github-release-plugin.git, jutzig/github-release-plugin",
27 |
28 | "scm:git:https://github.com/jutzig/github-release-plugin, jutzig/github-release-plugin",
29 | "scm:git|https://github.com/jutzig/github-release-plugin, jutzig/github-release-plugin",
30 | "https://github.com/jutzig/github-release-plugin, jutzig/github-release-plugin",
31 |
32 | "scm:git:http://github.com/jutzig/github-release-plugin.git/child, jutzig/github-release-plugin",
33 | "scm:git|http://github.com/jutzig/github-release-plugin.git/child, jutzig/github-release-plugin",
34 | "http://github.com/jutzig/github-release-plugin.git/child, jutzig/github-release-plugin",
35 |
36 | // Enterprise
37 | "scm:git:https://github.acme.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin",
38 | "scm:git|https://github.acme.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin",
39 | "https://github.acme.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin",
40 |
41 | "scm:git:http://github.acme.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin",
42 | "scm:git|http://github.acme.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin",
43 | "http://github.acme.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin",
44 |
45 | "scm:git:git@github.acme.com:jutzig/github-release-plugin.git, jutzig/github-release-plugin",
46 | "scm:git|git@github.acme.com:jutzig/github-release-plugin.git, jutzig/github-release-plugin",
47 | "git@github.acme.com:jutzig/github-release-plugin.git, jutzig/github-release-plugin",
48 |
49 | "scm:git:https://github.acme.com/jutzig/github-release-plugin, jutzig/github-release-plugin",
50 | "scm:git|https://github.acme.com/jutzig/github-release-plugin, jutzig/github-release-plugin",
51 | "https://github.acme.com/jutzig/github-release-plugin, jutzig/github-release-plugin",
52 |
53 | "scm:git:http://github.acme.com/jutzig/github-release-plugin.git/child, jutzig/github-release-plugin",
54 | "scm:git|http://github.acme.com/jutzig/github-release-plugin.git/child, jutzig/github-release-plugin",
55 | "http://github.acme.com/jutzig/github-release-plugin.git/child, jutzig/github-release-plugin"
56 | })
57 | void testComputeRepositoryId(String scmString, String expectedRepositoryId) {
58 | assertEquals(expectedRepositoryId, UploadMojo.computeRepositoryId(scmString));
59 | }
60 |
61 | @ParameterizedTest(name = "{0} should resolve to {1} endpoint")
62 | @MethodSource("scmFixture")
63 | void testGithubEndpoint(Scm scm, String expectedEndpoint) {
64 | assertEquals(expectedEndpoint, UploadMojo.computeGithubApiEndpoint(scm));
65 | }
66 |
67 | @Test
68 | void testGuessPreRelease() {
69 | assertTrue(UploadMojo.guessPreRelease("1.0-SNAPSHOT"));
70 | assertTrue(UploadMojo.guessPreRelease("1.0-alpha"));
71 | assertTrue(UploadMojo.guessPreRelease("1.0-alpha-1"));
72 | assertTrue(UploadMojo.guessPreRelease("1.0-beta"));
73 | assertTrue(UploadMojo.guessPreRelease("1.0-beta-1"));
74 | assertTrue(UploadMojo.guessPreRelease("1.0-RC"));
75 | assertTrue(UploadMojo.guessPreRelease("1.0-RC1"));
76 | assertTrue(UploadMojo.guessPreRelease("1.0-rc1"));
77 | assertTrue(UploadMojo.guessPreRelease("1.0-rc-1"));
78 |
79 | assertFalse(UploadMojo.guessPreRelease("1"));
80 | assertFalse(UploadMojo.guessPreRelease("1.0"));
81 | }
82 |
83 | private static Stream scmFixture() {
84 | return Stream.of(
85 | // Public GitHub
86 | Arguments.of(scmWithConnectionString("scm:git:https://github.com/jutzig/github-release-plugin.git"), "https://api.github.com"),
87 | Arguments.of(scmWithConnectionString("scm:git|https://github.com/jutzig/github-release-plugin.git"), "https://api.github.com"),
88 | Arguments.of(scmWithConnectionString("https://github.com/jutzig/github-release-plugin.git"), "https://api.github.com"),
89 |
90 | Arguments.of(scmWithConnectionString("scm:git:http://github.com/jutzig/github-release-plugin.git"), "https://api.github.com"),
91 | Arguments.of(scmWithConnectionString("scm:git|http://github.com/jutzig/github-release-plugin.git"), "https://api.github.com"),
92 | Arguments.of(scmWithConnectionString("http://github.com/jutzig/github-release-plugin.git"), "https://api.github.com"),
93 |
94 | Arguments.of(scmWithConnectionString("scm:git:git@github.com:jutzig/github-release-plugin.git"), "https://api.github.com"),
95 | Arguments.of(scmWithConnectionString("scm:git|git@github.com:jutzig/github-release-plugin.git"), "https://api.github.com"),
96 | Arguments.of(scmWithConnectionString("git@github.com:jutzig/github-release-plugin.git"), "https://api.github.com"),
97 |
98 | Arguments.of(scmWithConnectionString("scm:git:https://github.com/jutzig/github-release-plugin"), "https://api.github.com"),
99 | Arguments.of(scmWithConnectionString("scm:git|https://github.com/jutzig/github-release-plugin"), "https://api.github.com"),
100 | Arguments.of(scmWithConnectionString("https://github.com/jutzig/github-release-plugin"), "https://api.github.com"),
101 |
102 | Arguments.of(scmWithConnectionString("scm:git:http://github.com/jutzig/github-release-plugin.git/child"), "https://api.github.com"),
103 | Arguments.of(scmWithConnectionString("scm:git|http://github.com/jutzig/github-release-plugin.git/child"), "https://api.github.com"),
104 | Arguments.of(scmWithConnectionString("http://github.com/jutzig/github-release-plugin.git/child"), "https://api.github.com"),
105 |
106 | // GitHub Enterprise
107 | Arguments.of(scmWithConnectionString("scm:git:https://github.acme.com/jutzig/github-release-plugin.git"), "https://github.acme.com/api/v3"),
108 | Arguments.of(scmWithConnectionString("scm:git|https://github.acme.com/jutzig/github-release-plugin.git"), "https://github.acme.com/api/v3"),
109 | Arguments.of(scmWithConnectionString("https://github.acme.com/jutzig/github-release-plugin.git"), "https://github.acme.com/api/v3"),
110 |
111 | Arguments.of(scmWithConnectionString("scm:git:http://github.acme.com/jutzig/github-release-plugin.git"), "http://github.acme.com/api/v3"),
112 | Arguments.of(scmWithConnectionString("scm:git|http://github.acme.com/jutzig/github-release-plugin.git"), "http://github.acme.com/api/v3"),
113 | Arguments.of(scmWithConnectionString("http://github.acme.com/jutzig/github-release-plugin.git"), "http://github.acme.com/api/v3"),
114 |
115 | Arguments.of(scmWithConnectionString("scm:git:git@github.acme.com:jutzig/github-release-plugin.git"), "https://github.acme.com/api/v3"),
116 | Arguments.of(scmWithConnectionString("scm:git|git@github.acme.com:jutzig/github-release-plugin.git"), "https://github.acme.com/api/v3"),
117 | Arguments.of(scmWithConnectionString("git@github.acme.com:jutzig/github-release-plugin.git"), "https://github.acme.com/api/v3"),
118 |
119 | Arguments.of(scmWithConnectionString("scm:git:https://github.acme.com/jutzig/github-release-plugin"), "https://github.acme.com/api/v3"),
120 | Arguments.of(scmWithConnectionString("scm:git|https://github.acme.com/jutzig/github-release-plugin"), "https://github.acme.com/api/v3"),
121 | Arguments.of(scmWithConnectionString("https://github.acme.com/jutzig/github-release-plugin"), "https://github.acme.com/api/v3"),
122 |
123 | Arguments.of(scmWithConnectionString("scm:git:http://github.acme.com/jutzig/github-release-plugin.git/child"), "http://github.acme.com/api/v3"),
124 | Arguments.of(scmWithConnectionString("scm:git|http://github.acme.com/jutzig/github-release-plugin.git/child"), "http://github.acme.com/api/v3"),
125 | Arguments.of(scmWithConnectionString("http://github.acme.com/jutzig/github-release-plugin.git/child"), "http://github.acme.com/api/v3"),
126 |
127 | // Fallback to public
128 | Arguments.of(null, "https://api.github.com")
129 | );
130 | }
131 |
132 | private static Scm scmWithConnectionString(String connection) {
133 | Scm scm = new Scm();
134 | scm.setConnection(connection);
135 | return scm;
136 | }
137 | }
138 |
--------------------------------------------------------------------------------