From c197ffb9a717f80680f7c9d55d8caa833eee8f02 Mon Sep 17 00:00:00 2001 From: Andres Almiray Date: Wed, 7 Apr 2021 18:12:42 +0200 Subject: [PATCH] Support closing milestones upon release --- .../org/jreleaser/engine/sign/Signer.java | 4 + .../java/org/jreleaser/model/GitService.java | 48 ++++++---- .../org/jreleaser/model/JReleaserModel.java | 3 +- .../java/org/jreleaser/model/Milestone.java | 90 +++++++++++++++++++ .../model/validation/GitServiceValidator.java | 14 +++ .../util/AbstractJReleaserLogger.java | 16 ++-- .../java/org/jreleaser/util/Constants.java | 1 + .../org/jreleaser/workflow/WorkflowImpl.java | 4 +- .../gradle/plugin/dsl/GitService.groovy | 4 + .../gradle/plugin/dsl/Milestone.groovy | 33 +++++++ .../internal/dsl/AbstractGitService.groovy | 6 ++ .../plugin/internal/dsl/GiteaImpl.groovy | 4 + .../plugin/internal/dsl/GithubImpl.groovy | 4 + .../plugin/internal/dsl/GitlabImpl.groovy | 4 + .../plugin/internal/dsl/MilestoneImpl.groovy | 57 ++++++++++++ .../jreleaser/maven/plugin/GitService.java | 22 +++-- .../org/jreleaser/maven/plugin/Milestone.java | 52 +++++++++++ .../internal/JReleaserModelConverter.java | 9 ++ sdks/gitea-java-sdk/gitea-java-sdk.gradle | 4 + .../java/org/jreleaser/sdk/gitea/Gitea.java | 32 ++++++- .../jreleaser/sdk/gitea/GiteaReleaser.java | 16 +++- .../org/jreleaser/sdk/gitea/api/GiteaAPI.java | 11 ++- .../jreleaser/sdk/gitea/api/GtMilestone.java | 55 ++++++++++++ .../java/org/jreleaser/sdk/github/Github.java | 19 ++++ .../jreleaser/sdk/github/GithubReleaser.java | 16 +++- .../java/org/jreleaser/sdk/gitlab/Gitlab.java | 36 ++++++++ .../jreleaser/sdk/gitlab/GitlabReleaser.java | 16 +++- .../jreleaser/sdk/gitlab/api/GitlabAPI.java | 7 ++ .../jreleaser/sdk/gitlab/api/Milestone.java | 82 +++++++++++++++++ 29 files changed, 627 insertions(+), 42 deletions(-) create mode 100644 core/jreleaser-model/src/main/java/org/jreleaser/model/Milestone.java create mode 100644 plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/dsl/Milestone.groovy create mode 100644 plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/MilestoneImpl.groovy create mode 100644 plugins/jreleaser-maven-plugin/src/main/java/org/jreleaser/maven/plugin/Milestone.java create mode 100644 sdks/gitea-java-sdk/src/main/java/org/jreleaser/sdk/gitea/api/GtMilestone.java create mode 100644 sdks/gitlab-java-sdk/src/main/java/org/jreleaser/sdk/gitlab/api/Milestone.java diff --git a/core/jreleaser-engine/src/main/java/org/jreleaser/engine/sign/Signer.java b/core/jreleaser-engine/src/main/java/org/jreleaser/engine/sign/Signer.java index c27bab11..764b36f0 100644 --- a/core/jreleaser-engine/src/main/java/org/jreleaser/engine/sign/Signer.java +++ b/core/jreleaser-engine/src/main/java/org/jreleaser/engine/sign/Signer.java @@ -85,6 +85,8 @@ public class Signer { List files = collectArtifacts(context, keyring); if (files.isEmpty()) { context.getLogger().info("No files configured for signing. Skipping"); + context.getLogger().restorePrefix(); + context.getLogger().decreaseIndent(); return; } @@ -94,6 +96,8 @@ public class Signer { if (files.isEmpty()) { context.getLogger().info("All signatures are up-to-date and valid. Skipping"); + context.getLogger().restorePrefix(); + context.getLogger().decreaseIndent(); return; } diff --git a/core/jreleaser-model/src/main/java/org/jreleaser/model/GitService.java b/core/jreleaser-model/src/main/java/org/jreleaser/model/GitService.java index 34221442..800dc440 100644 --- a/core/jreleaser-model/src/main/java/org/jreleaser/model/GitService.java +++ b/core/jreleaser-model/src/main/java/org/jreleaser/model/GitService.java @@ -40,6 +40,9 @@ public abstract class GitService implements Releaser, CommitAuthorProvider, Owne private static final String TAG_EARLY_ACCESS = "early-access"; private final String serviceName; + private final Changelog changelog = new Changelog(); + private final Milestone milestone = new Milestone(); + private final CommitAuthor commitAuthor = new CommitAuthor(); protected Boolean enabled; private String host; private String owner; @@ -54,10 +57,8 @@ public abstract class GitService implements Releaser, CommitAuthorProvider, Owne private String token; private String tagName = "v{{projectVersion}}"; private String releaseName = "Release {{tagName}}"; - private CommitAuthor commitAuthor = new CommitAuthor(); private boolean sign; private boolean skipTagging; - private Changelog changelog = new Changelog(); private boolean overwrite; private boolean allowUploadToExisting; private String apiEndpoint; @@ -94,16 +95,9 @@ public abstract class GitService implements Releaser, CommitAuthorProvider, Owne this.overwrite = service.overwrite; this.allowUploadToExisting = service.allowUploadToExisting; this.apiEndpoint = service.apiEndpoint; - this.commitAuthor.setAll(service.commitAuthor); - this.changelog.setAll(service.changelog); - } - - public void setCachedTagName(String cachedTagName) { - this.cachedTagName = cachedTagName; - } - - public void setCachedReleaseName(String cachedReleaseName) { - this.cachedReleaseName = cachedReleaseName; + setCommitAuthor(service.commitAuthor); + setChangelog(service.changelog); + setMilestone(service.milestone); } public String getCanonicalRepoName() { @@ -125,6 +119,8 @@ public abstract class GitService implements Releaser, CommitAuthorProvider, Owne if (isBlank(cachedTagName)) { cachedTagName = applyTemplate(new StringReader(tagName), props(project)); + } else if (cachedTagName.contains("{{")) { + cachedTagName = applyTemplate(new StringReader(cachedTagName), props(project)); } return cachedTagName; @@ -135,7 +131,7 @@ public abstract class GitService implements Releaser, CommitAuthorProvider, Owne return TAG_EARLY_ACCESS; } - return getResolvedTagName(project); + return cachedTagName; } public String getResolvedReleaseName(Project project) { @@ -145,11 +141,17 @@ public abstract class GitService implements Releaser, CommitAuthorProvider, Owne if (isBlank(cachedReleaseName)) { cachedReleaseName = applyTemplate(new StringReader(releaseName), props(project)); + } else if (cachedReleaseName.contains("{{")) { + cachedReleaseName = applyTemplate(new StringReader(cachedReleaseName), props(project)); } return cachedReleaseName; } + public String getEffectiveReleaseName() { + return cachedReleaseName; + } + public String getResolvedRepoUrl(Project project) { return applyTemplate(new StringReader(repoUrlFormat), props(project)); } @@ -306,7 +308,7 @@ public abstract class GitService implements Releaser, CommitAuthorProvider, Owne @Override public void setCommitAuthor(CommitAuthor commitAuthor) { - this.commitAuthor = commitAuthor; + this.commitAuthor.setAll(commitAuthor); } public boolean isSign() { @@ -330,7 +332,15 @@ public abstract class GitService implements Releaser, CommitAuthorProvider, Owne } public void setChangelog(Changelog changelog) { - this.changelog = changelog; + this.changelog.setAll(changelog); + } + + public Milestone getMilestone() { + return milestone; + } + + public void setMilestone(Milestone milestone) { + this.milestone.setAll(milestone); } public boolean isOverwrite() { @@ -381,11 +391,11 @@ public abstract class GitService implements Releaser, CommitAuthorProvider, Owne map.put("allowUploadToExisting", allowUploadToExisting); map.put("apiEndpoint", apiEndpoint); map.put("changelog", changelog.asMap()); + map.put("milestone", milestone.asMap()); return map; } - - private Map props(Project project) { + public Map props(Project project) { // duplicate from JReleaserModel to avoid endless recursion Map props = new LinkedHashMap<>(); props.put(Constants.KEY_PROJECT_NAME, project.getName()); @@ -412,6 +422,7 @@ public abstract class GitService implements Releaser, CommitAuthorProvider, Owne props.put(Constants.KEY_CANONICAL_REPO_NAME, getCanonicalRepoName()); props.put(Constants.KEY_TAG_NAME, project.isSnapshot() ? TAG_EARLY_ACCESS : cachedTagName); props.put(Constants.KEY_RELEASE_NAME, cachedReleaseName); + props.put(Constants.KEY_MILESTONE_NAME, milestone.getEffectiveName()); return props; } @@ -423,7 +434,8 @@ public abstract class GitService implements Releaser, CommitAuthorProvider, Owne props.put(Constants.KEY_REVERSE_REPO_HOST, getReverseRepoHost()); props.put(Constants.KEY_CANONICAL_REPO_NAME, getCanonicalRepoName()); props.put(Constants.KEY_TAG_NAME, getEffectiveTagName(project)); - props.put(Constants.KEY_RELEASE_NAME, getResolvedReleaseName(project)); + props.put(Constants.KEY_RELEASE_NAME, getEffectiveReleaseName()); + props.put(Constants.KEY_MILESTONE_NAME, milestone.getEffectiveName()); props.put(Constants.KEY_REPO_URL, getResolvedRepoUrl(project)); props.put(Constants.KEY_COMMIT_URL, getResolvedCommitUrl(project)); props.put(Constants.KEY_RELEASE_NOTES_URL, getResolvedReleaseNotesUrl(project)); diff --git a/core/jreleaser-model/src/main/java/org/jreleaser/model/JReleaserModel.java b/core/jreleaser-model/src/main/java/org/jreleaser/model/JReleaserModel.java index 2cd02600..8a4478fa 100644 --- a/core/jreleaser-model/src/main/java/org/jreleaser/model/JReleaserModel.java +++ b/core/jreleaser-model/src/main/java/org/jreleaser/model/JReleaserModel.java @@ -212,7 +212,8 @@ public class JReleaserModel implements Domain { props.put(Constants.KEY_REPO_NAME, service.getName()); props.put(Constants.KEY_REPO_BRANCH, service.getBranch()); props.put(Constants.KEY_TAG_NAME, service.getEffectiveTagName(project)); - props.put(Constants.KEY_RELEASE_NAME, service.getResolvedReleaseName(project)); + props.put(Constants.KEY_RELEASE_NAME, service.getEffectiveReleaseName()); + props.put(Constants.KEY_MILESTONE_NAME, service.getMilestone().getEffectiveName()); props.put(Constants.KEY_REVERSE_REPO_HOST, service.getReverseRepoHost()); props.put(Constants.KEY_CANONICAL_REPO_NAME, service.getCanonicalRepoName()); props.put(Constants.KEY_REPO_URL, service.getResolvedRepoUrl(project)); diff --git a/core/jreleaser-model/src/main/java/org/jreleaser/model/Milestone.java b/core/jreleaser-model/src/main/java/org/jreleaser/model/Milestone.java new file mode 100644 index 00000000..a584007f --- /dev/null +++ b/core/jreleaser-model/src/main/java/org/jreleaser/model/Milestone.java @@ -0,0 +1,90 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2020-2021 Andres Almiray. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jreleaser.model; + +import org.jreleaser.util.Env; + +import java.io.StringReader; +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.jreleaser.util.MustacheUtils.applyTemplate; +import static org.jreleaser.util.StringUtils.isBlank; + +/** + * @author Andres Almiray + * @since 0.1.0 + */ +public class Milestone implements Domain { + public static final String MILESTONE_NAME = "MILESTONE_NAME"; + + private Boolean close; + private String name = "{{ tagName }}"; + + private String cachedName; + + void setAll(Milestone changelog) { + this.close = changelog.close; + this.name = changelog.name; + } + + public String getEffectiveName() { + return cachedName; + } + + public String getResolvedName(Map props) { + if (isBlank(cachedName)) { + cachedName = Env.resolve(MILESTONE_NAME, cachedName); + } + + if (isBlank(cachedName)) { + cachedName = applyTemplate(new StringReader(name), props); + } else if (cachedName.contains("{{")) { + cachedName = applyTemplate(new StringReader(cachedName), props); + } + + return cachedName; + } + + public Boolean isClose() { + return close == null || close; + } + + public void setClose(Boolean close) { + this.close = close; + } + + public boolean isCloseSet() { + return close != null; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Map asMap() { + Map map = new LinkedHashMap<>(); + map.put("name", name); + map.put("close", isClose()); + return map; + } +} diff --git a/core/jreleaser-model/src/main/java/org/jreleaser/model/validation/GitServiceValidator.java b/core/jreleaser-model/src/main/java/org/jreleaser/model/validation/GitServiceValidator.java index d9a429f6..43e74fb6 100644 --- a/core/jreleaser-model/src/main/java/org/jreleaser/model/validation/GitServiceValidator.java +++ b/core/jreleaser-model/src/main/java/org/jreleaser/model/validation/GitServiceValidator.java @@ -28,6 +28,7 @@ import java.util.List; import static org.jreleaser.model.GitService.RELEASE_NAME; import static org.jreleaser.model.GitService.TAG_NAME; +import static org.jreleaser.model.Milestone.MILESTONE_NAME; import static org.jreleaser.util.StringUtils.isBlank; /** @@ -92,9 +93,22 @@ public abstract class GitServiceValidator extends Validator { service.getCommitAuthor().setEmail("jreleaser-bot@jreleaser.org"); } + // milestone + service.getMilestone().setName( + checkProperty(context.getModel().getEnvironment(), + MILESTONE_NAME, + service.getServiceName() + ".milestone.name", + service.getMilestone().getName(), + errors)); + + if (isBlank(service.getMilestone().getName())) { + service.getMilestone().setName("{{ tagName }}"); + } + // eager resolve service.getResolvedTagName(project); service.getResolvedReleaseName(project); + service.getMilestone().getResolvedName(service.props(project)); if (project.isSnapshot()) { service.setReleaseName(StringUtils.capitalize(project.getName()) + " Early-Access"); diff --git a/core/jreleaser-utils/src/main/java/org/jreleaser/util/AbstractJReleaserLogger.java b/core/jreleaser-utils/src/main/java/org/jreleaser/util/AbstractJReleaserLogger.java index 8e26cbc1..9c7dcb34 100644 --- a/core/jreleaser-utils/src/main/java/org/jreleaser/util/AbstractJReleaserLogger.java +++ b/core/jreleaser-utils/src/main/java/org/jreleaser/util/AbstractJReleaserLogger.java @@ -17,30 +17,32 @@ */ package org.jreleaser.util; +import java.util.Stack; + /** * @author Andres Almiray * @since 0.1.0 */ public abstract class AbstractJReleaserLogger implements JReleaserLogger { + private final Stack prefix = new Stack<>(); private String indent = ""; - private String prefix = null; - private String previousPrefix = null; @Override public void reset() { - this.prefix = this.previousPrefix = null; + this.prefix.clear(); this.indent = ""; } @Override public void setPrefix(String prefix) { - this.previousPrefix = this.prefix; - this.prefix = prefix; + this.prefix.push(prefix); } @Override public void restorePrefix() { - this.prefix = this.previousPrefix; + if (!this.prefix.isEmpty()) { + this.prefix.pop(); + } } @Override @@ -56,6 +58,6 @@ public abstract class AbstractJReleaserLogger implements JReleaserLogger { } protected String formatMessage(String message) { - return indent + (prefix != null ? "[" + prefix + "] " : "") + message; + return indent + (!prefix.isEmpty() ? "[" + prefix.peek() + "] " : "") + message; } } diff --git a/core/jreleaser-utils/src/main/java/org/jreleaser/util/Constants.java b/core/jreleaser-utils/src/main/java/org/jreleaser/util/Constants.java index 0bd039da..dc49c03f 100644 --- a/core/jreleaser-utils/src/main/java/org/jreleaser/util/Constants.java +++ b/core/jreleaser-utils/src/main/java/org/jreleaser/util/Constants.java @@ -58,6 +58,7 @@ public interface Constants { String KEY_REPO_BRANCH = "repoBranch"; String KEY_TAG_NAME = "tagName"; String KEY_RELEASE_NAME = "releaseName"; + String KEY_MILESTONE_NAME = "milestoneName"; String KEY_CANONICAL_REPO_NAME = "repoCanonicalName"; String KEY_REPO_URL = "repoUrl"; String KEY_COMMIT_URL = "commitsUrl"; diff --git a/core/jreleaser-workflow/src/main/java/org/jreleaser/workflow/WorkflowImpl.java b/core/jreleaser-workflow/src/main/java/org/jreleaser/workflow/WorkflowImpl.java index b2aa5687..d56fa995 100644 --- a/core/jreleaser-workflow/src/main/java/org/jreleaser/workflow/WorkflowImpl.java +++ b/core/jreleaser-workflow/src/main/java/org/jreleaser/workflow/WorkflowImpl.java @@ -35,6 +35,7 @@ import java.util.Properties; import static org.jreleaser.util.Constants.KEY_COMMIT_FULL_HASH; import static org.jreleaser.util.Constants.KEY_COMMIT_SHORT_HASH; +import static org.jreleaser.util.Constants.KEY_MILESTONE_NAME; import static org.jreleaser.util.Constants.KEY_PROJECT_SNAPSHOT; import static org.jreleaser.util.Constants.KEY_PROJECT_VERSION; import static org.jreleaser.util.Constants.KEY_RELEASE_NAME; @@ -102,7 +103,8 @@ class WorkflowImpl implements Workflow { props.put(KEY_PROJECT_VERSION, project.getResolvedVersion()); props.put(KEY_PROJECT_SNAPSHOT, String.valueOf(project.isSnapshot())); props.put(KEY_TAG_NAME, model.getRelease().getGitService().getEffectiveTagName(project)); - props.put(KEY_RELEASE_NAME, model.getRelease().getGitService().getResolvedReleaseName(project)); + props.put(KEY_RELEASE_NAME, model.getRelease().getGitService().getEffectiveReleaseName()); + props.put(KEY_MILESTONE_NAME, model.getRelease().getGitService().getMilestone().getEffectiveName()); Map resolvedExtraProperties = project.getResolvedExtraProperties(); safePut("project" + capitalize(KEY_VERSION_MAJOR), resolvedExtraProperties, props); diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/dsl/GitService.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/dsl/GitService.groovy index 7b84406e..2e145639 100644 --- a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/dsl/GitService.groovy +++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/dsl/GitService.groovy @@ -66,6 +66,10 @@ interface GitService extends Releaser { void changelog(Action action) + Milestone getMilestone() + + void milestone(Action action) + CommitAuthor getCommitAuthor() void commitAuthor(Action action) diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/dsl/Milestone.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/dsl/Milestone.groovy new file mode 100644 index 00000000..50c50b42 --- /dev/null +++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/dsl/Milestone.groovy @@ -0,0 +1,33 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2020-2021 Andres Almiray. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jreleaser.gradle.plugin.dsl + +import groovy.transform.CompileStatic +import org.gradle.api.provider.Property + +/** + * + * @author Andres Almiray + * @since 0.1.0 + */ +@CompileStatic +interface Milestone { + Property getClose() + + Property getName() +} \ No newline at end of file diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/AbstractGitService.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/AbstractGitService.groovy index 7ec92ae2..103be488 100644 --- a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/AbstractGitService.groovy +++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/AbstractGitService.groovy @@ -26,6 +26,7 @@ import org.gradle.api.tasks.Internal import org.jreleaser.gradle.plugin.dsl.Changelog import org.jreleaser.gradle.plugin.dsl.CommitAuthor import org.jreleaser.gradle.plugin.dsl.GitService +import org.jreleaser.gradle.plugin.dsl.Milestone import javax.inject.Inject @@ -108,6 +109,11 @@ abstract class AbstractGitService implements GitService { action.execute(changelog) } + @Override + void milestone(Action action) { + action.execute(milestone) + } + @Override void commitAuthor(Action action) { action.execute(commitAuthor) diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/GiteaImpl.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/GiteaImpl.groovy index a1088ec1..8d8bc441 100644 --- a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/GiteaImpl.groovy +++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/GiteaImpl.groovy @@ -37,6 +37,7 @@ class GiteaImpl extends AbstractGitService implements Gitea { final Property draft final Property prerelease final ChangelogImpl changelog + final MilestoneImpl milestone final CommitAuthorImpl commitAuthor @Inject @@ -47,6 +48,7 @@ class GiteaImpl extends AbstractGitService implements Gitea { prerelease = objects.property(Boolean).convention(Providers.notDefined()) changelog = objects.newInstance(ChangelogImpl, objects) + milestone = objects.newInstance(MilestoneImpl, objects) commitAuthor = objects.newInstance(CommitAuthorImpl, objects) } @@ -57,6 +59,7 @@ class GiteaImpl extends AbstractGitService implements Gitea { draft.present || prerelease.present || changelog.isSet() || + milestone.isSet() || commitAuthor.isSet() } @@ -67,6 +70,7 @@ class GiteaImpl extends AbstractGitService implements Gitea { service.draft = draft.getOrElse(false) service.prerelease = prerelease.getOrElse(false) if (changelog.isSet()) service.changelog = changelog.toModel() + if (milestone.isSet()) service.milestone = milestone.toModel() if (commitAuthor.isSet()) service.commitAuthor = commitAuthor.toModel() service } diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/GithubImpl.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/GithubImpl.groovy index 24e6886a..733c9513 100644 --- a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/GithubImpl.groovy +++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/GithubImpl.groovy @@ -37,6 +37,7 @@ class GithubImpl extends AbstractGitService implements Github { final Property draft final Property prerelease final ChangelogImpl changelog + final MilestoneImpl milestone final CommitAuthorImpl commitAuthor @Inject @@ -47,6 +48,7 @@ class GithubImpl extends AbstractGitService implements Github { prerelease = objects.property(Boolean).convention(Providers.notDefined()) changelog = objects.newInstance(ChangelogImpl, objects) + milestone = objects.newInstance(MilestoneImpl, objects) commitAuthor = objects.newInstance(CommitAuthorImpl, objects) } @@ -57,6 +59,7 @@ class GithubImpl extends AbstractGitService implements Github { draft.present || prerelease.present || changelog.isSet() || + milestone.isSet() || commitAuthor.isSet() } @@ -67,6 +70,7 @@ class GithubImpl extends AbstractGitService implements Github { service.draft = draft.getOrElse(false) service.prerelease = prerelease.getOrElse(false) if (changelog.isSet()) service.changelog = changelog.toModel() + if (milestone.isSet()) service.milestone = milestone.toModel() if (commitAuthor.isSet()) service.commitAuthor = commitAuthor.toModel() service } diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/GitlabImpl.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/GitlabImpl.groovy index 4f043b54..722d2747 100644 --- a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/GitlabImpl.groovy +++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/GitlabImpl.groovy @@ -35,6 +35,7 @@ import javax.inject.Inject class GitlabImpl extends AbstractGitService implements Gitlab { final Property ref final ChangelogImpl changelog + final MilestoneImpl milestone final CommitAuthorImpl commitAuthor @Inject @@ -43,6 +44,7 @@ class GitlabImpl extends AbstractGitService implements Gitlab { ref = objects.property(String).convention(Providers.notDefined()) changelog = objects.newInstance(ChangelogImpl, objects) + milestone = objects.newInstance(MilestoneImpl, objects) commitAuthor = objects.newInstance(CommitAuthorImpl, objects) } @@ -51,6 +53,7 @@ class GitlabImpl extends AbstractGitService implements Gitlab { super.isSet() || ref.present || changelog.isSet() || + milestone.isSet() || commitAuthor.isSet() } @@ -59,6 +62,7 @@ class GitlabImpl extends AbstractGitService implements Gitlab { toModel(service) if (ref.present) service.ref = ref.get() if (changelog.isSet()) service.changelog = changelog.toModel() + if (milestone.isSet()) service.milestone = milestone.toModel() if (commitAuthor.isSet()) service.commitAuthor = commitAuthor.toModel() service } diff --git a/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/MilestoneImpl.groovy b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/MilestoneImpl.groovy new file mode 100644 index 00000000..aacea02f --- /dev/null +++ b/plugins/jreleaser-gradle-plugin/src/main/groovy/org/jreleaser/gradle/plugin/internal/dsl/MilestoneImpl.groovy @@ -0,0 +1,57 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2020-2021 Andres Almiray. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jreleaser.gradle.plugin.internal.dsl + +import groovy.transform.CompileStatic +import org.gradle.api.internal.provider.Providers +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Internal +import org.jreleaser.gradle.plugin.dsl.Milestone + +import javax.inject.Inject + +/** + * + * @author Andres Almiray + * @since 0.1.0 + */ +@CompileStatic +class MilestoneImpl implements Milestone { + final Property close + final Property name + + @Inject + MilestoneImpl(ObjectFactory objects) { + close = objects.property(Boolean).convention(Providers.notDefined()) + name = objects.property(String).convention(Providers.notDefined()) + } + + @Internal + boolean isSet() { + close.present || + name.present + } + + org.jreleaser.model.Milestone toModel() { + org.jreleaser.model.Milestone milestone = new org.jreleaser.model.Milestone() + if (close.present) milestone.close = close.get() + if (name.present) milestone.name = name.get() + milestone + } +} diff --git a/plugins/jreleaser-maven-plugin/src/main/java/org/jreleaser/maven/plugin/GitService.java b/plugins/jreleaser-maven-plugin/src/main/java/org/jreleaser/maven/plugin/GitService.java index cccb9152..6e605d3e 100644 --- a/plugins/jreleaser-maven-plugin/src/main/java/org/jreleaser/maven/plugin/GitService.java +++ b/plugins/jreleaser-maven-plugin/src/main/java/org/jreleaser/maven/plugin/GitService.java @@ -22,6 +22,9 @@ package org.jreleaser.maven.plugin; * @since 0.1.0 */ public abstract class GitService implements Releaser { + private final CommitAuthor commitAuthor = new CommitAuthor(); + private final Changelog changelog = new Changelog(); + private final Milestone milestone = new Milestone(); protected Boolean enabled; private String host; private String owner; @@ -36,10 +39,8 @@ public abstract class GitService implements Releaser { private String token; private String tagName; private String releaseName; - private CommitAuthor commitAuthor = new CommitAuthor(); private boolean sign; private boolean skipTagging; - private Changelog changelog = new Changelog(); private boolean overwrite; private boolean allowUploadToExisting; private String apiEndpoint; @@ -59,13 +60,14 @@ public abstract class GitService implements Releaser { this.token = service.token; this.tagName = service.tagName; this.releaseName = service.releaseName; - this.commitAuthor.setAll(service.commitAuthor); this.sign = service.sign; this.skipTagging = service.skipTagging; this.overwrite = service.overwrite; this.allowUploadToExisting = service.allowUploadToExisting; this.apiEndpoint = service.apiEndpoint; - this.changelog.setAll(service.changelog); + setCommitAuthor(service.commitAuthor); + setChangelog(service.changelog); + setMilestone(service.milestone); } @Override @@ -192,7 +194,7 @@ public abstract class GitService implements Releaser { } public void setCommitAuthor(CommitAuthor commitAuthor) { - this.commitAuthor = commitAuthor; + this.commitAuthor.setAll(commitAuthor); } public boolean isSign() { @@ -216,7 +218,15 @@ public abstract class GitService implements Releaser { } public void setChangelog(Changelog changelog) { - this.changelog = changelog; + this.changelog.setAll(changelog); + } + + public Milestone getMilestone() { + return milestone; + } + + public void setMilestone(Milestone milestone) { + this.milestone.setAll(milestone); } public boolean isOverwrite() { diff --git a/plugins/jreleaser-maven-plugin/src/main/java/org/jreleaser/maven/plugin/Milestone.java b/plugins/jreleaser-maven-plugin/src/main/java/org/jreleaser/maven/plugin/Milestone.java new file mode 100644 index 00000000..021f7934 --- /dev/null +++ b/plugins/jreleaser-maven-plugin/src/main/java/org/jreleaser/maven/plugin/Milestone.java @@ -0,0 +1,52 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2020-2021 Andres Almiray. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jreleaser.maven.plugin; + +/** + * @author Andres Almiray + * @since 0.1.0 + */ +public class Milestone { + private Boolean close; + private String name; + + void setAll(Milestone changelog) { + this.close = changelog.close; + this.name = changelog.name; + } + + public Boolean isClose() { + return close == null || close; + } + + public void setClose(Boolean close) { + this.close = close; + } + + public boolean isCloseSet() { + return close != null; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/plugins/jreleaser-maven-plugin/src/main/java/org/jreleaser/maven/plugin/internal/JReleaserModelConverter.java b/plugins/jreleaser-maven-plugin/src/main/java/org/jreleaser/maven/plugin/internal/JReleaserModelConverter.java index d57570e4..f79c464b 100644 --- a/plugins/jreleaser-maven-plugin/src/main/java/org/jreleaser/maven/plugin/internal/JReleaserModelConverter.java +++ b/plugins/jreleaser-maven-plugin/src/main/java/org/jreleaser/maven/plugin/internal/JReleaserModelConverter.java @@ -38,6 +38,7 @@ import org.jreleaser.maven.plugin.Java; import org.jreleaser.maven.plugin.Jbang; import org.jreleaser.maven.plugin.Jreleaser; import org.jreleaser.maven.plugin.Mail; +import org.jreleaser.maven.plugin.Milestone; import org.jreleaser.maven.plugin.Packagers; import org.jreleaser.maven.plugin.Plug; import org.jreleaser.maven.plugin.Project; @@ -176,6 +177,7 @@ public final class JReleaserModelConverter { s.setAllowUploadToExisting(service.isAllowUploadToExisting()); s.setApiEndpoint(service.getApiEndpoint()); s.setChangelog(convertChangelog(service.getChangelog())); + s.setMilestone(convertMilestone(service.getMilestone())); } private static org.jreleaser.model.CommitAuthor convertCommitAuthor(CommitAuthor commitAuthor) { @@ -193,6 +195,13 @@ public final class JReleaserModelConverter { return c; } + private static org.jreleaser.model.Milestone convertMilestone(Milestone milestone) { + org.jreleaser.model.Milestone m = new org.jreleaser.model.Milestone(); + m.setClose(milestone.isClose()); + m.setName(milestone.getName()); + return m; + } + private static org.jreleaser.model.Packagers convertPackagers(Packagers packagers) { org.jreleaser.model.Packagers p = new org.jreleaser.model.Packagers(); if (packagers.getBrew().isSet()) p.setBrew(convertBrew(packagers.getBrew())); diff --git a/sdks/gitea-java-sdk/gitea-java-sdk.gradle b/sdks/gitea-java-sdk/gitea-java-sdk.gradle index 9c5301c4..2191fe9e 100644 --- a/sdks/gitea-java-sdk/gitea-java-sdk.gradle +++ b/sdks/gitea-java-sdk/gitea-java-sdk.gradle @@ -26,6 +26,10 @@ dependencies { api "io.github.openfeign:feign-core:$feignVersion" api "io.github.openfeign:feign-jackson:$feignVersion" + api("io.github.openfeign:feign-httpclient:$feignVersion") { + exclude group: 'commons-logging', module: 'commons-logging' + } + api "org.slf4j:jcl-over-slf4j:$slf4jVersion" api "com.fasterxml.jackson.core:jackson-core:$jacksonVersion" api "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion" api "io.github.openfeign.form:feign-form:$feignFormVersion" diff --git a/sdks/gitea-java-sdk/src/main/java/org/jreleaser/sdk/gitea/Gitea.java b/sdks/gitea-java-sdk/src/main/java/org/jreleaser/sdk/gitea/Gitea.java index d3e002cb..a7744e01 100644 --- a/sdks/gitea-java-sdk/src/main/java/org/jreleaser/sdk/gitea/Gitea.java +++ b/sdks/gitea-java-sdk/src/main/java/org/jreleaser/sdk/gitea/Gitea.java @@ -26,12 +26,14 @@ import feign.Feign; import feign.Request; import feign.form.FormData; import feign.form.FormEncoder; +import feign.httpclient.ApacheHttpClient; import feign.jackson.JacksonDecoder; import feign.jackson.JacksonEncoder; import org.apache.tika.Tika; import org.apache.tika.mime.MediaType; import org.jreleaser.sdk.gitea.api.GiteaAPI; import org.jreleaser.sdk.gitea.api.GiteaAPIException; +import org.jreleaser.sdk.gitea.api.GtMilestone; import org.jreleaser.sdk.gitea.api.GtOrganization; import org.jreleaser.sdk.gitea.api.GtRelease; import org.jreleaser.sdk.gitea.api.GtRepository; @@ -43,10 +45,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.TimeUnit; import static java.util.Objects.requireNonNull; -import static org.jreleaser.util.StringUtils.isBlank; import static org.jreleaser.util.StringUtils.requireNonBlank; /** @@ -80,6 +82,7 @@ class Gitea { this.logger = logger; this.api = Feign.builder() + .client(new ApacheHttpClient()) .encoder(new FormEncoder(new JacksonEncoder(objectMapper))) .decoder(new JacksonDecoder(objectMapper)) .requestInterceptor(template -> template.header("Authorization", String.format("token %s", token))) @@ -101,6 +104,33 @@ class Gitea { } } + Optional findMilestoneByName(String owner, String repo, String milestoneName) { + logger.debug("Lookup milestone '{}' on {}/{}", milestoneName, owner, repo); + + try { + GtMilestone milestone = api.findMilestoneByTitle(owner, repo, milestoneName); + + if (milestone == null) { + return Optional.empty(); + } + + return "open".equals(milestone.getState()) ? Optional.of(milestone) : Optional.empty(); + } catch (GiteaAPIException e) { + if (e.isNotFound()) { + // ok + return Optional.empty(); + } + throw e; + } + } + + void closeMilestone(String owner, String repo, GtMilestone milestone) throws IOException { + logger.debug("Closing milestone '{}' on {}/{}", milestone.getTitle(), owner, repo); + + api.updateMilestone(CollectionUtils.map() + .e("state", "closed"), owner, repo, milestone.getId()); + } + GtRepository createRepository(String owner, String repo) { logger.debug("Creating repository {}/{}", owner, repo); diff --git a/sdks/gitea-java-sdk/src/main/java/org/jreleaser/sdk/gitea/GiteaReleaser.java b/sdks/gitea-java-sdk/src/main/java/org/jreleaser/sdk/gitea/GiteaReleaser.java index 74113970..4715051c 100644 --- a/sdks/gitea-java-sdk/src/main/java/org/jreleaser/sdk/gitea/GiteaReleaser.java +++ b/sdks/gitea-java-sdk/src/main/java/org/jreleaser/sdk/gitea/GiteaReleaser.java @@ -23,6 +23,7 @@ import org.jreleaser.model.releaser.spi.Releaser; import org.jreleaser.model.releaser.spi.Repository; import org.jreleaser.sdk.git.GitSdk; import org.jreleaser.sdk.gitea.api.GiteaAPIException; +import org.jreleaser.sdk.gitea.api.GtMilestone; import org.jreleaser.sdk.gitea.api.GtRelease; import org.jreleaser.sdk.gitea.api.GtRepository; @@ -31,6 +32,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Optional; /** * @author Andres Almiray @@ -123,20 +125,30 @@ public class GiteaReleaser implements Releaser { } // local tag - if (deleteTags || !context.getModel().getRelease().getGitService().isSkipTagging()) { + if (deleteTags || !gitea.isSkipTagging()) { context.getLogger().debug("Tagging local repository with {}", tagName); GitSdk.of(context).tag(tagName, true); } // remote tag/release GtRelease release = new GtRelease(); - release.setName(gitea.getResolvedReleaseName(context.getModel().getProject())); + release.setName(gitea.getEffectiveReleaseName()); release.setTagName(gitea.getEffectiveTagName(context.getModel().getProject())); release.setTargetCommitish(gitea.getTargetCommitish()); release.setBody(changelog); release = api.createRelease(gitea.getOwner(), gitea.getName(), release); api.uploadAssets(gitea.getOwner(), gitea.getName(), release, assets); + + Optional milestone = api.findMilestoneByName( + gitea.getOwner(), + gitea.getName(), + gitea.getMilestone().getEffectiveName()); + if (milestone.isPresent()) { + api.closeMilestone(gitea.getOwner(), + gitea.getName(), + milestone.get()); + } } private void deleteTags(Gitea api, String owner, String repo, String tagName) { diff --git a/sdks/gitea-java-sdk/src/main/java/org/jreleaser/sdk/gitea/api/GiteaAPI.java b/sdks/gitea-java-sdk/src/main/java/org/jreleaser/sdk/gitea/api/GiteaAPI.java index 996a9c07..5af6b352 100644 --- a/sdks/gitea-java-sdk/src/main/java/org/jreleaser/sdk/gitea/api/GiteaAPI.java +++ b/sdks/gitea-java-sdk/src/main/java/org/jreleaser/sdk/gitea/api/GiteaAPI.java @@ -37,11 +37,11 @@ public interface GiteaAPI { @RequestLine("POST /orgs/{org}/repos") @Headers("Content-Type: application/json") - GtRepository createRepository(Map data, @Param("org") String org); + GtRepository createRepository(Map data, @Param("org") String org); @RequestLine("POST /user/repos") @Headers("Content-Type: application/json") - GtRepository createRepository(Map data); + GtRepository createRepository(Map data); @RequestLine("GET /repos/{owner}/{repo}/releases/tags/{tag}") GtRelease getReleaseByTagName(@Param("owner") String owner, @Param("repo") String repo, @Param("tag") String tag); @@ -59,4 +59,11 @@ public interface GiteaAPI { @RequestLine("POST /repos/{owner}/{repo}/releases/{id}/assets") @Headers("Content-Type: multipart/form-data") GtAttachment uploadAsset(@Param("owner") String owner, @Param("repo") String repo, @Param("id") Integer id, @Param("attachment") FormData file); + + @RequestLine("GET /repos/{owner}/{repo}/milestones/{milestoneName}") + GtMilestone findMilestoneByTitle(@Param("owner") String owner, @Param("repo") String repo, @Param("milestoneName") String milestoneName); + + @RequestLine("PATCH /repos/{owner}/{repo}/milestones/{id}") + @Headers("Content-Type: application/json") + void updateMilestone(Map params, @Param("owner") String owner, @Param("repo") String repo, @Param("id") Integer id); } diff --git a/sdks/gitea-java-sdk/src/main/java/org/jreleaser/sdk/gitea/api/GtMilestone.java b/sdks/gitea-java-sdk/src/main/java/org/jreleaser/sdk/gitea/api/GtMilestone.java new file mode 100644 index 00000000..8038ed93 --- /dev/null +++ b/sdks/gitea-java-sdk/src/main/java/org/jreleaser/sdk/gitea/api/GtMilestone.java @@ -0,0 +1,55 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2020-2021 Andres Almiray. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jreleaser.sdk.gitea.api; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * @author Andres Almiray + * @since 0.1.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class GtMilestone { + private Integer id; + private String title; + private String state; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } +} \ No newline at end of file diff --git a/sdks/github-java-sdk/src/main/java/org/jreleaser/sdk/github/Github.java b/sdks/github-java-sdk/src/main/java/org/jreleaser/sdk/github/Github.java index a1d91639..44697230 100644 --- a/sdks/github-java-sdk/src/main/java/org/jreleaser/sdk/github/Github.java +++ b/sdks/github-java-sdk/src/main/java/org/jreleaser/sdk/github/Github.java @@ -24,6 +24,8 @@ import org.kohsuke.github.GHAsset; import org.kohsuke.github.GHDiscussion; import org.kohsuke.github.GHException; import org.kohsuke.github.GHFileNotFoundException; +import org.kohsuke.github.GHIssueState; +import org.kohsuke.github.GHMilestone; import org.kohsuke.github.GHOrganization; import org.kohsuke.github.GHRelease; import org.kohsuke.github.GHReleaseBuilder; @@ -31,6 +33,7 @@ import org.kohsuke.github.GHRepository; import org.kohsuke.github.GHTeam; import org.kohsuke.github.GitHub; import org.kohsuke.github.GitHubBuilder; +import org.kohsuke.github.PagedIterable; import java.io.IOException; import java.nio.file.Files; @@ -93,6 +96,22 @@ class Github { .create(); } + Optional findMilestoneByName(String owner, String repo, String milestoneName) throws IOException { + logger.debug("Lookup milestone '{}' on {}/{}", milestoneName, owner, repo); + + GHRepository repository = findRepository(owner, repo); + PagedIterable milestones = repository.listMilestones(GHIssueState.OPEN); + return StreamSupport.stream(milestones.spliterator(), false) + .filter(m -> milestoneName.equals(m.getTitle())) + .findFirst(); + } + + void closeMilestone(String owner, String repo, GHMilestone milestone) throws IOException { + logger.debug("Closing milestone '{}' on {}/{}", milestone.getTitle(), owner, repo); + + milestone.close(); + } + GHRelease findReleaseByTag(String repo, String tagName) throws IOException { logger.debug("Fetching release on {} with tag {}", repo, tagName); return github.getRepository(repo) diff --git a/sdks/github-java-sdk/src/main/java/org/jreleaser/sdk/github/GithubReleaser.java b/sdks/github-java-sdk/src/main/java/org/jreleaser/sdk/github/GithubReleaser.java index 457588bb..e33615eb 100644 --- a/sdks/github-java-sdk/src/main/java/org/jreleaser/sdk/github/GithubReleaser.java +++ b/sdks/github-java-sdk/src/main/java/org/jreleaser/sdk/github/GithubReleaser.java @@ -22,6 +22,7 @@ import org.jreleaser.model.releaser.spi.ReleaseException; import org.jreleaser.model.releaser.spi.Releaser; import org.jreleaser.model.releaser.spi.Repository; import org.jreleaser.sdk.git.GitSdk; +import org.kohsuke.github.GHMilestone; import org.kohsuke.github.GHRelease; import org.kohsuke.github.GHRepository; @@ -30,6 +31,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Optional; /** * @author Andres Almiray @@ -120,7 +122,7 @@ public class GithubReleaser implements Releaser { } // local tag - if (deleteTags || !context.getModel().getRelease().getGitService().isSkipTagging()) { + if (deleteTags || !github.isSkipTagging()) { context.getLogger().debug("Tagging local repository with {}", tagName); GitSdk.of(context).tag(tagName, true); } @@ -129,12 +131,22 @@ public class GithubReleaser implements Releaser { GHRelease release = api.createRelease(github.getCanonicalRepoName(), github.getEffectiveTagName(context.getModel().getProject())) .commitish(github.getTargetCommitish()) - .name(github.getResolvedReleaseName(context.getModel().getProject())) + .name(github.getEffectiveReleaseName()) .draft(github.isDraft()) .prerelease(github.isPrerelease()) .body(changelog) .create(); api.uploadAssets(release, assets); + + Optional milestone = api.findMilestoneByName( + github.getOwner(), + github.getName(), + github.getMilestone().getEffectiveName()); + if (milestone.isPresent()) { + api.closeMilestone(github.getOwner(), + github.getName(), + milestone.get()); + } } private void deleteTags(Github api, String repo, String tagName) { diff --git a/sdks/gitlab-java-sdk/src/main/java/org/jreleaser/sdk/gitlab/Gitlab.java b/sdks/gitlab-java-sdk/src/main/java/org/jreleaser/sdk/gitlab/Gitlab.java index 769604a3..fdf9426e 100644 --- a/sdks/gitlab-java-sdk/src/main/java/org/jreleaser/sdk/gitlab/Gitlab.java +++ b/sdks/gitlab-java-sdk/src/main/java/org/jreleaser/sdk/gitlab/Gitlab.java @@ -33,6 +33,7 @@ import org.apache.tika.mime.MediaType; import org.jreleaser.sdk.gitlab.api.FileUpload; import org.jreleaser.sdk.gitlab.api.GitlabAPI; import org.jreleaser.sdk.gitlab.api.GitlabAPIException; +import org.jreleaser.sdk.gitlab.api.Milestone; import org.jreleaser.sdk.gitlab.api.Project; import org.jreleaser.sdk.gitlab.api.Release; import org.jreleaser.sdk.gitlab.api.User; @@ -44,6 +45,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.concurrent.TimeUnit; import static java.util.Objects.requireNonNull; @@ -114,6 +116,40 @@ class Gitlab { return projects.get(0); } + Optional findMilestoneByName(String owner, String repo, String milestoneName) throws IOException { + logger.debug("Lookup milestone '{}' on {}/{}", milestoneName, owner, repo); + + Project project = getProject(repo); + + try { + List milestones = api.findMilestoneByTitle(project.getId(), CollectionUtils.map() + .e("title", milestoneName)); + + if (milestones == null || milestones.isEmpty()) { + return Optional.empty(); + } + + Milestone milestone = milestones.get(0); + return "active".equals(milestone.getState()) ? Optional.of(milestone) : Optional.empty(); + } catch (GitlabAPIException e) { + if (e.isNotFound() || e.isForbidden()) { + // ok + return Optional.empty(); + } + throw e; + } + } + + void closeMilestone(String owner, String repo, Milestone milestone) throws IOException { + logger.debug("Closing milestone '{}' on {}/{}", milestone.getTitle(), owner, repo); + + Project project = getProject(repo); + + api.updateMilestone(CollectionUtils.map() + .e("state_event", "close"), + project.getId(), milestone.getId()); + } + Project createProject(String owner, String repo) throws IOException { logger.debug("Creating project {}/{}", owner, repo); diff --git a/sdks/gitlab-java-sdk/src/main/java/org/jreleaser/sdk/gitlab/GitlabReleaser.java b/sdks/gitlab-java-sdk/src/main/java/org/jreleaser/sdk/gitlab/GitlabReleaser.java index 13f59c94..838e5ae0 100644 --- a/sdks/gitlab-java-sdk/src/main/java/org/jreleaser/sdk/gitlab/GitlabReleaser.java +++ b/sdks/gitlab-java-sdk/src/main/java/org/jreleaser/sdk/gitlab/GitlabReleaser.java @@ -24,6 +24,7 @@ import org.jreleaser.model.releaser.spi.Repository; import org.jreleaser.sdk.git.GitSdk; import org.jreleaser.sdk.gitlab.api.FileUpload; import org.jreleaser.sdk.gitlab.api.GitlabAPIException; +import org.jreleaser.sdk.gitlab.api.Milestone; import org.jreleaser.sdk.gitlab.api.Project; import org.jreleaser.sdk.gitlab.api.Release; @@ -32,6 +33,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Optional; /** * @author Andres Almiray @@ -134,7 +136,7 @@ public class GitlabReleaser implements Releaser { } // local tag - if (deleteTags || !context.getModel().getRelease().getGitService().isSkipTagging()) { + if (deleteTags || !gitlab.isSkipTagging()) { context.getLogger().debug("Tagging local repository with {}", tagName); GitSdk.of(context).tag(tagName, true); } @@ -142,7 +144,7 @@ public class GitlabReleaser implements Releaser { List uploads = api.uploadAssets(gitlab.getOwner(), gitlab.getName(), assets); Release release = new Release(); - release.setName(gitlab.getResolvedReleaseName(context.getModel().getProject())); + release.setName(gitlab.getEffectiveReleaseName()); release.setTagName(gitlab.getEffectiveTagName(context.getModel().getProject())); release.setRef(gitlab.getRef()); release.setDescription(changelog); @@ -150,6 +152,16 @@ public class GitlabReleaser implements Releaser { // remote tag/release api.createRelease(gitlab.getOwner(), gitlab.getName(), release); api.linkAssets(gitlab.getOwner(), gitlab.getName(), release, uploads); + + Optional milestone = api.findMilestoneByName( + gitlab.getOwner(), + gitlab.getName(), + gitlab.getMilestone().getEffectiveName()); + if (milestone.isPresent()) { + api.closeMilestone(gitlab.getOwner(), + gitlab.getName(), + milestone.get()); + } } private void deleteTags(Gitlab api, String owner, String repo, String tagName) { diff --git a/sdks/gitlab-java-sdk/src/main/java/org/jreleaser/sdk/gitlab/api/GitlabAPI.java b/sdks/gitlab-java-sdk/src/main/java/org/jreleaser/sdk/gitlab/api/GitlabAPI.java index e1467e7e..833f3b82 100644 --- a/sdks/gitlab-java-sdk/src/main/java/org/jreleaser/sdk/gitlab/api/GitlabAPI.java +++ b/sdks/gitlab-java-sdk/src/main/java/org/jreleaser/sdk/gitlab/api/GitlabAPI.java @@ -61,4 +61,11 @@ public interface GitlabAPI { @RequestLine("POST /projects/{projectId}/releases/{tagName}/assets/links") @Headers("Content-Type: multipart/form-data") Link linkAsset(LinkRequest link, @Param("projectId") Integer projectId, @Param("tagName") String tagName); + + @RequestLine("GET /projects/{projectId}/milestones") + List findMilestoneByTitle(@Param("projectId") Integer projectId, @QueryMap Map queryMap); + + @RequestLine("PUT /projects/{projectId}/milestones/{milestoneId}") + @Headers("Content-Type: application/json") + void updateMilestone(Map params, @Param("projectId") Integer projectId, @Param("milestoneId") Integer milestoneId); } diff --git a/sdks/gitlab-java-sdk/src/main/java/org/jreleaser/sdk/gitlab/api/Milestone.java b/sdks/gitlab-java-sdk/src/main/java/org/jreleaser/sdk/gitlab/api/Milestone.java new file mode 100644 index 00000000..b4f35c81 --- /dev/null +++ b/sdks/gitlab-java-sdk/src/main/java/org/jreleaser/sdk/gitlab/api/Milestone.java @@ -0,0 +1,82 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright 2020-2021 Andres Almiray. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jreleaser.sdk.gitlab.api; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * @author Andres Almiray + * @since 0.1.0 + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class Milestone { + private Integer id; + private Integer iid; + private Integer projectId; + private String title; + private String description; + private String state; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getIid() { + return iid; + } + + public void setIid(Integer iid) { + this.iid = iid; + } + + public Integer getProjectId() { + return projectId; + } + + public void setProjectId(Integer projectId) { + this.projectId = projectId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } +}