Skip to content

Commit

Permalink
feat(release): Attach milestone to issues on release. Resolves #948
Browse files Browse the repository at this point in the history
  • Loading branch information
aalmiray committed Oct 27, 2022
1 parent 84336e5 commit 247b198
Show file tree
Hide file tree
Showing 17 changed files with 351 additions and 36 deletions.
@@ -0,0 +1,43 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright 2020-2022 The JReleaser authors.
*
* 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.api.common;

import java.util.Locale;

import static org.jreleaser.util.StringUtils.isBlank;

/**
* @author Andres Almiray
* @since 1.3.0
*/
public enum Apply {
NEVER,
ALWAYS,
WARN,
FORCE;

@Override
public String toString() {
return name().toLowerCase(Locale.ENGLISH);
}

public static Apply of(String str) {
if (isBlank(str)) return null;
return Apply.valueOf(str.toUpperCase(Locale.ENGLISH).trim());
}
}
Expand Up @@ -19,6 +19,7 @@

import org.jreleaser.model.Active;
import org.jreleaser.model.UpdateSection;
import org.jreleaser.model.api.common.Apply;
import org.jreleaser.model.api.common.CommitAuthorAware;
import org.jreleaser.model.api.common.Domain;
import org.jreleaser.model.api.common.EnabledAware;
Expand All @@ -45,6 +46,7 @@ public interface Releaser extends Domain, EnabledAware, CommitAuthorAware, Owner
String SKIP_RELEASE = "SKIP_RELEASE";
String BRANCH = "BRANCH";
String PRERELEASE_PATTERN = "PRERELEASE_PATTERN";
String MILESTONE_NAME = "MILESTONE_NAME";

String getServiceName();

Expand Down Expand Up @@ -129,8 +131,6 @@ interface Prerelease extends Domain, EnabledAware {
}

interface Milestone extends Domain {
String MILESTONE_NAME = "MILESTONE_NAME";

boolean isClose();

String getName();
Expand All @@ -141,6 +141,8 @@ interface Issues extends Domain, EnabledAware {

Label getLabel();

Apply getApplyMilestone();

interface Label extends Domain {
String getName();

Expand Down
Expand Up @@ -546,7 +546,8 @@ git.push.release = pushing to {}
git.push.tag = pushing tag to remote, dryrun = {}
git.repository.lookup = lookup repository {}/{}
git.repository.create = creating repository {}/{}
git.milestone.lookup = lookup milestone '{}' on {}/{}
git.milestone.lookup = lookup open milestone '{}' on {}/{}
git.milestone.lookup.closed = lookup closed milestone '{}' on {}/{}
git.milestone.close = closing milestone '{}' on {}/{}
git.project.create = creating project {}/{}
git.fetch.current.user = fetching current user
Expand Down Expand Up @@ -587,6 +588,9 @@ git.issue.release = marking issue #{} as released
git.issue.release.mark = Marking {} issue(s) as released
git.issue.label = adding label {} to issue #{}
git.issue.comment = commenting on issue #{}
git.issue.milestone.apply = applying milestone {} to issue #{}
git.issue.milestone.warn = Issue #{} already has a milestone: {}. Skipping
git.issue.milestone.force = Forcing milestone {} on issue #{}. Previous milestone was {}
ERROR_git_organization_not_exist = Organization {} does not exist
ERROR_git_team_not_exist = Team {} does not exist
git.releaser.releasing = Releasing to {}
Expand Down
Expand Up @@ -21,6 +21,7 @@
import org.jreleaser.model.Active;
import org.jreleaser.model.Constants;
import org.jreleaser.model.UpdateSection;
import org.jreleaser.model.api.common.Apply;
import org.jreleaser.model.internal.JReleaserModel;
import org.jreleaser.model.internal.common.AbstractModelObject;
import org.jreleaser.model.internal.common.CommitAuthor;
Expand Down Expand Up @@ -965,7 +966,7 @@ public void merge(Milestone source) {
}

public String getConfiguredName() {
return Env.env(org.jreleaser.model.api.release.Releaser.Milestone.MILESTONE_NAME, cachedName);
return Env.env(org.jreleaser.model.api.release.Releaser.MILESTONE_NAME, cachedName);
}

public String getResolvedName(Map<String, Object> props) {
Expand Down Expand Up @@ -1017,6 +1018,7 @@ public Map<String, Object> asMap(boolean full) {

public static final class Issues extends AbstractModelObject<Issues> implements Domain, EnabledAware {
private final Label label = new Label();
private Apply applyMilestone;
private String comment;
private Boolean enabled;

Expand All @@ -1026,6 +1028,11 @@ public String getComment() {
return comment;
}

@Override
public Apply getApplyMilestone() {
return applyMilestone;
}

@Override
public Label getLabel() {
return label.asImmutable();
Expand All @@ -1050,6 +1057,7 @@ public org.jreleaser.model.api.release.Releaser.Issues asImmutable() {
public void merge(Issues source) {
this.comment = merge(this.comment, source.comment);
this.enabled = merge(this.enabled, source.enabled);
this.applyMilestone = merge(this.applyMilestone, source.applyMilestone);
setLabel(source.label);
}

Expand Down Expand Up @@ -1084,12 +1092,25 @@ public void setLabel(Label label) {
this.label.merge(label);
}

public Apply getApplyMilestone() {
return applyMilestone;
}

public void setApplyMilestone(Apply applyMilestone) {
this.applyMilestone = applyMilestone;
}

public void setApplyMilestone(String str) {
setApplyMilestone(Apply.of(str));
}

@Override
public Map<String, Object> asMap(boolean full) {
Map<String, Object> map = new LinkedHashMap<>();
map.put("enabled", isEnabled());
map.put("comment", comment);
map.put("label", label.asMap(full));
map.put("applyMilestone", applyMilestone);
return map;
}

Expand Down
Expand Up @@ -44,7 +44,7 @@
import static java.lang.System.lineSeparator;
import static java.util.stream.Collectors.groupingBy;
import static org.jreleaser.model.api.release.Releaser.BRANCH;
import static org.jreleaser.model.api.release.Releaser.Milestone.MILESTONE_NAME;
import static org.jreleaser.model.api.release.Releaser.MILESTONE_NAME;
import static org.jreleaser.model.api.release.Releaser.OVERWRITE;
import static org.jreleaser.model.api.release.Releaser.RELEASE_NAME;
import static org.jreleaser.model.api.release.Releaser.SKIP_RELEASE;
Expand Down
Expand Up @@ -24,6 +24,7 @@ import org.gradle.api.provider.SetProperty
import org.jreleaser.gradle.plugin.dsl.common.CommitAuthor
import org.jreleaser.model.Active
import org.jreleaser.model.UpdateSection
import org.jreleaser.model.api.common.Apply

/**
*
Expand Down Expand Up @@ -147,6 +148,10 @@ interface BaseReleaser extends Releaser {

Property<String> getComment()

Property<Apply> getApplyMilestone()

void setApplyMilestone(String str)

void label(Action<? super Label> action)

void label(@DelegatesTo(strategy = Closure.DELEGATE_FIRST, value = Label) Closure<Void> action)
Expand Down
Expand Up @@ -29,6 +29,7 @@ import org.jreleaser.gradle.plugin.dsl.release.BaseReleaser
import org.jreleaser.gradle.plugin.dsl.release.Changelog
import org.jreleaser.model.Active
import org.jreleaser.model.UpdateSection
import org.jreleaser.model.api.common.Apply
import org.kordamp.gradle.util.ConfigureUtil

import javax.inject.Inject
Expand Down Expand Up @@ -328,19 +329,29 @@ abstract class BaseReleaserImpl implements BaseReleaser {
static class IssuesImpl implements Issues {
final Property<Boolean> enabled
final Property<String> comment
final Property<Apply> applyMilestone
final LabelImpl label

@Inject
IssuesImpl(ObjectFactory objects) {
enabled = objects.property(Boolean).convention(Providers.<Boolean> notDefined())
comment = objects.property(String).convention(Providers.<String> notDefined())
applyMilestone = objects.property(Apply).convention(Providers.<Apply> notDefined())
label = objects.newInstance(LabelImpl, objects)
}

@Override
void setApplyMilestone(String str) {
if (isNotBlank(str)) {
applyMilestone.set(Apply.of(str.trim()))
}
}

@Internal
boolean isSet() {
enabled.present ||
comment.present ||
applyMilestone.present ||
label.isSet()
}

Expand All @@ -358,6 +369,7 @@ abstract class BaseReleaserImpl implements BaseReleaser {
org.jreleaser.model.internal.release.BaseReleaser.Issues issues = new org.jreleaser.model.internal.release.BaseReleaser.Issues()
if (enabled.present) issues.enabled = enabled.get()
if (comment.present) issues.comment = comment.get()
if (applyMilestone.present) issues.applyMilestone = applyMilestone.get()
if (label.isSet()) issues.label = label.toModel()
issues
}
Expand Down
Expand Up @@ -209,6 +209,26 @@ Optional<GtMilestone> findMilestoneByName(String owner, String repo, String mile
}
}

Optional<GtMilestone> findClosedMilestoneByName(String owner, String repo, String milestoneName) {
logger.debug(RB.$("git.milestone.lookup.closed"), milestoneName, owner, repo);

try {
GtMilestone milestone = api.findMilestoneByTitle(owner, repo, milestoneName);

if (milestone == null) {
return Optional.empty();
}

return "closed".equals(milestone.getState()) ? Optional.of(milestone) : Optional.empty();
} catch (RestAPIException e) {
if (e.isNotFound()) {
// ok
return Optional.empty();
}
throw e;
}
}

void closeMilestone(String owner, String repo, GtMilestone milestone) throws IOException {
logger.debug(RB.$("git.milestone.close"), milestone.getTitle(), owner, repo);

Expand Down Expand Up @@ -372,7 +392,7 @@ Optional<GtIssue> findIssue(String owner, String name, int issueNumber) throws I
}
}

void addLabelToIssue(String owner, String name, GtIssue issue, GtLabel label) throws IOException {
void addLabelToIssue(String owner, String name, GtIssue issue, GtLabel label) {
logger.debug(RB.$("git.issue.label", label.getName(), issue.getNumber()));

Map<String, List<Integer>> labels = new LinkedHashMap<>();
Expand All @@ -383,7 +403,7 @@ void addLabelToIssue(String owner, String name, GtIssue issue, GtLabel label) th
api.labelIssue(labels, owner, name, issue.getNumber());
}

void commentOnIssue(String owner, String name, GtIssue issue, String comment) throws IOException {
void commentOnIssue(String owner, String name, GtIssue issue, String comment) {
logger.debug(RB.$("git.issue.comment", issue.getNumber()));

Map<String, String> params = new LinkedHashMap<>();
Expand All @@ -392,6 +412,13 @@ void commentOnIssue(String owner, String name, GtIssue issue, String comment) th
api.commentIssue(params, owner, name, issue.getNumber());
}

void setMilestoneOnIssue(String owner, String name, GtIssue issue, GtMilestone milestone) {
Map<String, Object> params = new LinkedHashMap<>();
params.put("milestone", milestone.getId());

api.updateIssue(params, owner, name, issue.getNumber());
}

private List<GtLabel> listLabels(String owner, String repoName) throws IOException {
logger.debug(RB.$("git.list.labels"), owner, repoName);

Expand Down
Expand Up @@ -20,6 +20,7 @@
import org.jreleaser.bundle.RB;
import org.jreleaser.model.JReleaserException;
import org.jreleaser.model.UpdateSection;
import org.jreleaser.model.api.common.Apply;
import org.jreleaser.model.internal.JReleaserContext;
import org.jreleaser.model.internal.util.VersionUtils;
import org.jreleaser.model.spi.release.AbstractReleaser;
Expand Down Expand Up @@ -50,6 +51,7 @@

import static org.jreleaser.mustache.Templates.resolveTemplate;
import static org.jreleaser.util.StringUtils.capitalize;
import static org.jreleaser.util.StringUtils.uncapitalize;

/**
* @author Andres Almiray
Expand Down Expand Up @@ -330,19 +332,60 @@ private void updateIssues(org.jreleaser.model.internal.release.GiteaReleaser git
throw new IllegalStateException(RB.$("ERROR_git_releaser_fetch_label", tagName, labelName), e);
}

Optional<GtMilestone> milestone = Optional.empty();
Apply applyMilestone = gitea.getIssues().getApplyMilestone();
if (gitea.getMilestone().isClose() && !context.getModel().getProject().isSnapshot()) {
milestone = api.findMilestoneByName(
gitea.getOwner(),
gitea.getName(),
gitea.getMilestone().getEffectiveName());

if (!milestone.isPresent()) {
milestone = api.findClosedMilestoneByName(
gitea.getOwner(),
gitea.getName(),
gitea.getMilestone().getEffectiveName());
}
}

for (String issueNumber : issueNumbers) {
try {
Optional<GtIssue> op = api.findIssue(gitea.getOwner(), gitea.getName(), Integer.parseInt(issueNumber));
if (!op.isPresent()) continue;

GtIssue gtIssue = op.get();
if (gtIssue.getState().equals("closed") && gtIssue.getLabels().stream().noneMatch(l -> l.getName().equals(labelName))) {
context.getLogger().debug(RB.$("git.issue.release", issueNumber));
api.addLabelToIssue(gitea.getOwner(), gitea.getName(), gtIssue, gtLabel);
api.commentOnIssue(gitea.getOwner(), gitea.getName(), gtIssue, comment);
Optional<GtIssue> op = api.findIssue(gitea.getOwner(), gitea.getName(), Integer.parseInt(issueNumber));
if (!op.isPresent()) continue;

GtIssue gtIssue = op.get();
if (gtIssue.getState().equals("closed") && gtIssue.getLabels().stream().noneMatch(l -> l.getName().equals(labelName))) {
context.getLogger().debug(RB.$("git.issue.release", issueNumber));
api.addLabelToIssue(gitea.getOwner(), gitea.getName(), gtIssue, gtLabel);
api.commentOnIssue(gitea.getOwner(), gitea.getName(), gtIssue, comment);

milestone.ifPresent(gtMilestone -> applyMilestone(gitea, api, issueNumber, gtIssue, applyMilestone, gtMilestone));
}
}
}

private void applyMilestone(org.jreleaser.model.internal.release.GiteaReleaser gitea, Gitea api, String issueNumber, GtIssue gtIssue, Apply applyMilestone, GtMilestone targetMilestone) {
GtMilestone issueMilestone = gtIssue.getMilestone();
String targetMilestoneTitle = targetMilestone.getTitle();

if (null == issueMilestone) {
context.getLogger().debug(RB.$("git.issue.milestone.apply", targetMilestoneTitle, issueNumber));
api.setMilestoneOnIssue(gitea.getOwner(), gitea.getName(), gtIssue, targetMilestone);
} else {
String milestoneTitle = issueMilestone.getTitle();

if (applyMilestone == Apply.ALWAYS) {
context.getLogger().debug(uncapitalize(RB.$("git.issue.milestone.warn", issueNumber, milestoneTitle)));
} else if (applyMilestone == Apply.WARN) {
if (!milestoneTitle.equals(targetMilestoneTitle)) {
context.getLogger().warn(RB.$("git.issue.milestone.warn", issueNumber, milestoneTitle));
}
} else if (applyMilestone == Apply.FORCE) {
if (!milestoneTitle.equals(targetMilestoneTitle)) {
context.getLogger().warn(RB.$("git.issue.milestone.force", targetMilestoneTitle, issueNumber, milestoneTitle));
api.setMilestoneOnIssue(gitea.getOwner(), gitea.getName(), gtIssue, targetMilestone);
} else {
context.getLogger().debug(uncapitalize(RB.$("git.issue.milestone.warn", issueNumber, milestoneTitle)));
}
} catch (IOException e) {
throw new IllegalStateException(RB.$("ERROR_git_releaser_cannot_release", tagName, issueNumber), e);
}
}
}
Expand Down
Expand Up @@ -115,4 +115,8 @@ public interface GiteaAPI {
@RequestLine("POST /repos/{owner}/{repo}/issues/{issueNumber}/comments")
@Headers("Content-Type: application/json")
void commentIssue(Map<String, String> params, @Param("owner") String owner, @Param("repo") String repo, @Param("issueNumber") Integer issueNumber);

@RequestLine("PATCH /repos/{owner}/{repo}/issues/{index}")
@Headers("Content-Type: application/json")
void updateIssue(Map<String, Object> params, @Param("owner") String owner, @Param("repo") String repo, @Param("index") Integer index);
}

0 comments on commit 247b198

Please sign in to comment.