mirror of
https://github.com/jenkinsci/jenkins.git
synced 2026-11-15 13:02:19 +00:00
Make all CLI commands compatible with Pipeline where possible (#2874)
* [FIX JENKINS-30785] Generalize some CLI stuff to AbstractItem * Offering default methods on ParameterizedJob. * Javadoc typo. * Cleaner use of default methods in ParameterizedJob. * Need to pick up https://github.com/infradna/bridge-method-injector/pull/15 to be able to build. * Sketch of pulling disabled functionality into ParameterizedJob. * EnableJobCommandTest.groovy → EnableJobCommandTest.java, and replacing deprecated Remoting-based CLI calls with CLICommandInvoker. * All CLI commands could be broken by a missing CLI.*.shortDescription key on just one! * Forgot to move CLI method short descriptions to new package. * Needed a @CLIResolver for ParameterizedJob. Adding an OptionHandler while we are here. * Trying to fix up access-modifier versions; started failing in CI today for unknown reasons. * Introduced <p:makeDisabled/> by analogy with <p:config-disableBuild/>. * Using new type bounds. * access-modifier 1.11 released. * MatrixProject and MavenModuleSet both expect to have access to makeDisabled.jelly. * Trying to generalize some more. * Minor simplification. * [JENKINS-34716] Generalizing doPolling and schedulePolling. * isBuildable * Obsolete comment. * Updated comments. * bridge-method-injector 1.17 * Unfortunately AbstractProject.schedulePolling cannot delegate to SCMTriggerItem. * Making delete-builds and list-changes commands work with Pipeline. * [FIXED JENKINS-41527] Made console CLI command compatible with Pipeline. * Fixed set-build-description and set-build-display-name. * @oleg-nenashev agreed it would be clearer to explicitly mark commands as restricted, not APIs. * Updated tests to match slight message changes. * bridge-method-injector 1.17 * @olivergondza pointed out that RunRangeCommand is a better name than JobRangeCommand.
This commit is contained in:
1 parent
a8994d4fa5
commit
33afbcc87f
13 files changed
+104
-58
No files matched your search
@@ -12,7 +12,9 @@ import java.util.List;
|
||||
* {@link CLICommand} that acts on a series of {@link AbstractBuild}s.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
* @deprecated rather use {@link RunRangeCommand}
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class AbstractBuildRangeCommand extends CLICommand {
|
||||
@Argument(metaVar="JOB",usage="Name of the job to build",required=true,index=0)
|
||||
public AbstractProject<?,?> job;
|
||||
|
||||
@@ -25,7 +25,6 @@ package hudson.cli;
|
||||
|
||||
import hudson.Util;
|
||||
import hudson.console.ModelHyperlinkNote;
|
||||
import hudson.model.AbstractProject;
|
||||
import hudson.model.Cause.UserIdCause;
|
||||
import hudson.model.CauseAction;
|
||||
import hudson.model.Job;
|
||||
|
||||
@@ -2,10 +2,10 @@ package hudson.cli;
|
||||
|
||||
import hudson.Extension;
|
||||
import hudson.console.AnnotatedLargeText;
|
||||
import hudson.model.AbstractBuild;
|
||||
import hudson.model.AbstractProject;
|
||||
import hudson.model.Item;
|
||||
import hudson.model.Job;
|
||||
import hudson.model.PermalinkProjectAction.Permalink;
|
||||
import hudson.model.Run;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
@@ -29,7 +29,7 @@ public class ConsoleCommand extends CLICommand {
|
||||
}
|
||||
|
||||
@Argument(metaVar="JOB",usage="Name of the job",required=true)
|
||||
public AbstractProject<?,?> job;
|
||||
public Job<?,?> job;
|
||||
|
||||
@Argument(metaVar="BUILD",usage="Build number or permalink to point to the build. Defaults to the last build",required=false,index=1)
|
||||
public String build="lastBuild";
|
||||
@@ -43,7 +43,7 @@ public class ConsoleCommand extends CLICommand {
|
||||
protected int run() throws Exception {
|
||||
job.checkPermission(Item.BUILD);
|
||||
|
||||
AbstractBuild<?,?> run;
|
||||
Run<?,?> run;
|
||||
|
||||
try {
|
||||
int n = Integer.parseInt(build);
|
||||
@@ -54,7 +54,7 @@ public class ConsoleCommand extends CLICommand {
|
||||
// maybe a permalink?
|
||||
Permalink p = job.getPermalinks().get(build);
|
||||
if (p!=null) {
|
||||
run = (AbstractBuild)p.resolve(job);
|
||||
run = p.resolve(job);
|
||||
if (run==null)
|
||||
throw new IllegalStateException("Permalink "+build+" produced no build");
|
||||
} else {
|
||||
@@ -94,7 +94,7 @@ public class ConsoleCommand extends CLICommand {
|
||||
/**
|
||||
* Find the byte offset in the log input stream that marks "last N lines".
|
||||
*/
|
||||
private long seek(AbstractBuild<?, ?> run) throws IOException {
|
||||
private long seek(Run<?, ?> run) throws IOException {
|
||||
class RingBuffer {
|
||||
long[] lastNlines = new long[n];
|
||||
int ptr=0;
|
||||
|
||||
@@ -24,21 +24,23 @@
|
||||
package hudson.cli;
|
||||
|
||||
import hudson.Extension;
|
||||
import hudson.model.AbstractBuild;
|
||||
import hudson.model.Run;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.DoNotUse;
|
||||
|
||||
/**
|
||||
* Deletes builds records in a bulk.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
@Restricted(DoNotUse.class) // command implementation only
|
||||
@Extension
|
||||
public class DeleteBuildsCommand extends AbstractBuildRangeCommand {
|
||||
public class DeleteBuildsCommand extends RunRangeCommand {
|
||||
@Override
|
||||
public String getShortDescription() {
|
||||
return Messages.DeleteBuildsCommand_ShortDescription();
|
||||
@@ -52,12 +54,12 @@ public class DeleteBuildsCommand extends AbstractBuildRangeCommand {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int act(List<AbstractBuild<?, ?>> builds) throws IOException {
|
||||
protected int act(List<Run<?, ?>> builds) throws IOException {
|
||||
job.checkPermission(Run.DELETE);
|
||||
|
||||
final HashSet<Integer> hsBuilds = new HashSet<Integer>();
|
||||
|
||||
for (AbstractBuild build : builds) {
|
||||
for (Run<?, ?> build : builds) {
|
||||
if (!hsBuilds.contains(build.number)) {
|
||||
build.delete();
|
||||
hsBuilds.add(build.number);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package hudson.cli;
|
||||
|
||||
import hudson.Extension;
|
||||
import hudson.model.AbstractBuild;
|
||||
import hudson.model.Run;
|
||||
import hudson.scm.ChangeLogSet;
|
||||
import hudson.scm.ChangeLogSet.Entry;
|
||||
import hudson.util.QuotedStringTokenizer;
|
||||
import jenkins.scm.RunWithSCM;
|
||||
import org.kohsuke.args4j.Option;
|
||||
import org.kohsuke.stapler.export.Flavor;
|
||||
import org.kohsuke.stapler.export.Model;
|
||||
@@ -13,14 +14,17 @@ import org.kohsuke.stapler.export.ModelBuilder;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import org.kohsuke.accmod.Restricted;
|
||||
import org.kohsuke.accmod.restrictions.DoNotUse;
|
||||
|
||||
/**
|
||||
* Retrieves a change list for the specified builds.
|
||||
*
|
||||
* @author Kohsuke Kawaguchi
|
||||
*/
|
||||
@Restricted(DoNotUse.class) // command implementation only
|
||||
@Extension
|
||||
public class ListChangesCommand extends AbstractBuildRangeCommand {
|
||||
public class ListChangesCommand extends RunRangeCommand {
|
||||
@Override
|
||||
public String getShortDescription() {
|
||||
return Messages.ListChangesCommand_ShortDescription();
|
||||
@@ -39,40 +43,50 @@ public class ListChangesCommand extends AbstractBuildRangeCommand {
|
||||
public Format format = Format.PLAIN;
|
||||
|
||||
@Override
|
||||
protected int act(List<AbstractBuild<?, ?>> builds) throws IOException {
|
||||
protected int act(List<Run<?, ?>> builds) throws IOException {
|
||||
// Loading job for this CLI command requires Item.READ permission.
|
||||
// No other permission check needed.
|
||||
switch (format) {
|
||||
case XML:
|
||||
PrintWriter w = new PrintWriter(stdout);
|
||||
w.println("<changes>");
|
||||
for (AbstractBuild build : builds) {
|
||||
w.println("<build number='"+build.getNumber()+"'>");
|
||||
ChangeLogSet<?> cs = build.getChangeSet();
|
||||
Model p = new ModelBuilder().get(cs.getClass());
|
||||
p.writeTo(cs,Flavor.XML.createDataWriter(cs,w));
|
||||
w.println("</build>");
|
||||
for (Run<?, ?> build : builds) {
|
||||
if (build instanceof RunWithSCM) {
|
||||
w.println("<build number='" + build.getNumber() + "'>");
|
||||
for (ChangeLogSet<?> cs : ((RunWithSCM<?, ?>) build).getChangeSets()) {
|
||||
Model p = new ModelBuilder().get(cs.getClass());
|
||||
p.writeTo(cs, Flavor.XML.createDataWriter(cs, w));
|
||||
}
|
||||
w.println("</build>");
|
||||
}
|
||||
}
|
||||
w.println("</changes>");
|
||||
w.flush();
|
||||
break;
|
||||
case CSV:
|
||||
for (AbstractBuild build : builds) {
|
||||
ChangeLogSet<?> cs = build.getChangeSet();
|
||||
for (Entry e : cs) {
|
||||
stdout.printf("%s,%s%n",
|
||||
QuotedStringTokenizer.quote(e.getAuthor().getId()),
|
||||
QuotedStringTokenizer.quote(e.getMsg()));
|
||||
for (Run<?, ?> build : builds) {
|
||||
if (build instanceof RunWithSCM) {
|
||||
for (ChangeLogSet<?> cs : ((RunWithSCM<?, ?>) build).getChangeSets()) {
|
||||
for (Entry e : cs) {
|
||||
stdout.printf("%s,%s%n",
|
||||
QuotedStringTokenizer.quote(e.getAuthor().getId()),
|
||||
QuotedStringTokenizer.quote(e.getMsg()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PLAIN:
|
||||
for (AbstractBuild build : builds) {
|
||||
ChangeLogSet<?> cs = build.getChangeSet();
|
||||
for (Entry e : cs) {
|
||||
stdout.printf("%s\t%s%n",e.getAuthor(),e.getMsg());
|
||||
for (String p : e.getAffectedPaths())
|
||||
stdout.println(" "+p);
|
||||
for (Run<?, ?> build : builds) {
|
||||
if (build instanceof RunWithSCM) {
|
||||
for (ChangeLogSet<?> cs : ((RunWithSCM<?, ?>) build).getChangeSets()) {
|
||||
for (Entry e : cs) {
|
||||
stdout.printf("%s\t%s%n", e.getAuthor(), e.getMsg());
|
||||
for (String p : e.getAffectedPaths()) {
|
||||
stdout.println(" " + p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -29,6 +29,8 @@ import hudson.model.AbstractItem;
|
||||
import hudson.model.AbstractProject;
|
||||
import hudson.model.Item;
|
||||
|
||||
import hudson.model.Items;
|
||||
import hudson.model.TopLevelItem;
|
||||
import jenkins.model.Jenkins;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
|
||||
@@ -69,7 +71,6 @@ public class ReloadJobCommand extends CLICommand {
|
||||
AbstractItem job = null;
|
||||
|
||||
try {
|
||||
// TODO: JENKINS-30786
|
||||
Item item = jenkins.getItemByFullName(job_s);
|
||||
if (item instanceof AbstractItem) {
|
||||
job = (AbstractItem) item;
|
||||
@@ -78,11 +79,10 @@ public class ReloadJobCommand extends CLICommand {
|
||||
}
|
||||
|
||||
if(job == null) {
|
||||
// TODO: JENKINS-30785
|
||||
AbstractProject project = AbstractProject.findNearest(job_s);
|
||||
AbstractItem project = Items.findNearest(AbstractItem.class, job_s, jenkins);
|
||||
throw new IllegalArgumentException(project == null ?
|
||||
"No such job \u2018" + job_s + "\u2019 exists." :
|
||||
String.format("No such job \u2018%s\u2019 exists. Perhaps you meant \u2018%s\u2019?",
|
||||
"No such item \u2018" + job_s + "\u2019 exists." :
|
||||
String.format("No such item \u2018%s\u2019 exists. Perhaps you meant \u2018%s\u2019?",
|
||||
job_s, project.getFullName()));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package hudson.cli;
|
||||
|
||||
import hudson.model.Fingerprint.RangeSet;
|
||||
import hudson.model.Job;
|
||||
import hudson.model.Run;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link CLICommand} that acts on a series of {@link Run}s.
|
||||
* @since FIXME
|
||||
*/
|
||||
public abstract class RunRangeCommand extends CLICommand {
|
||||
@Argument(metaVar="JOB",usage="Name of the job to build",required=true,index=0)
|
||||
public Job<?,?> job;
|
||||
|
||||
@Argument(metaVar="RANGE",usage="Range of the build records to delete. 'N-M', 'N,M', or 'N'",required=true,index=1)
|
||||
public String range;
|
||||
|
||||
protected int run() throws Exception {
|
||||
RangeSet rs = RangeSet.fromString(range,false);
|
||||
|
||||
return act((List)job.getBuilds(rs));
|
||||
}
|
||||
|
||||
protected abstract int act(List<Run<?,?>> builds) throws IOException;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package hudson.cli;
|
||||
|
||||
import hudson.Extension;
|
||||
import hudson.model.AbstractProject;
|
||||
import hudson.model.Job;
|
||||
import hudson.model.Run;
|
||||
|
||||
import java.io.Serializable;
|
||||
@@ -18,7 +18,7 @@ public class SetBuildDescriptionCommand extends CLICommand implements Serializab
|
||||
}
|
||||
|
||||
@Argument(metaVar="JOB",usage="Name of the job to build",required=true,index=0)
|
||||
public transient AbstractProject<?,?> job;
|
||||
public transient Job<?,?> job;
|
||||
|
||||
@Argument(metaVar="BUILD#",usage="Number of the build",required=true,index=1)
|
||||
public int number;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package hudson.cli;
|
||||
|
||||
import hudson.Extension;
|
||||
import hudson.model.AbstractProject;
|
||||
import hudson.model.Job;
|
||||
import hudson.model.Run;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
@@ -18,7 +18,7 @@ public class SetBuildDisplayNameCommand extends CLICommand implements Serializab
|
||||
}
|
||||
|
||||
@Argument(metaVar="JOB", usage="Name of the job to build", required=true, index=0)
|
||||
public transient AbstractProject<?, ?> job;
|
||||
public transient Job<?, ?> job;
|
||||
|
||||
@Argument(metaVar="BUILD#", usage="Number of the build", required=true, index=1)
|
||||
public int number;
|
||||
|
||||
@@ -850,11 +850,11 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
|
||||
*/
|
||||
@CLIResolver
|
||||
public static AbstractItem resolveForCLI(
|
||||
@Argument(required=true,metaVar="NAME",usage="Job name") String name) throws CmdLineException {
|
||||
@Argument(required=true,metaVar="NAME",usage="Item name") String name) throws CmdLineException {
|
||||
// TODO can this (and its pseudo-override in AbstractProject) share code with GenericItemOptionHandler, used for explicit CLICommand’s rather than CLIMethod’s?
|
||||
AbstractItem item = Jenkins.getInstance().getItemByFullName(name, AbstractItem.class);
|
||||
if (item==null) {
|
||||
AbstractProject project = AbstractProject.findNearest(name); // TODO should be Items.findNearest
|
||||
AbstractItem project = Items.findNearest(AbstractItem.class, name, Jenkins.getInstance());
|
||||
throw new CmdLineException(null, project == null ? Messages.AbstractItem_NoSuchJobExistsWithoutSuggestion(name)
|
||||
: Messages.AbstractItem_NoSuchJobExists(name, project.getFullName()));
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ public class ReloadJobCommandTest {
|
||||
|
||||
assertThat(result, failedWith(3));
|
||||
assertThat(result, hasNoStandardOutput());
|
||||
assertThat(result.stderr(), containsString("ERROR: No such job \u2018aProject\u2019 exists."));
|
||||
assertThat(result.stderr(), containsString("ERROR: No such item ‘aProject’ exists."));
|
||||
|
||||
assertThat(project.scheduleBuild2(0).get().getLog(), containsString("echo 1"));
|
||||
}
|
||||
@@ -121,7 +121,7 @@ public class ReloadJobCommandTest {
|
||||
.invokeWithArgs("never_created");
|
||||
assertThat(result, failedWith(3));
|
||||
assertThat(result, hasNoStandardOutput());
|
||||
assertThat(result.stderr(), containsString("ERROR: No such job \u2018never_created\u2019 exists."));
|
||||
assertThat(result.stderr(), containsString("ERROR: No such item ‘never_created’ exists."));
|
||||
}
|
||||
|
||||
@Test public void reloadJobShouldFailIfJobDoesNotExistButNearExists() throws Exception {
|
||||
@@ -133,7 +133,7 @@ public class ReloadJobCommandTest {
|
||||
.invokeWithArgs("never_created1");
|
||||
assertThat(result, failedWith(3));
|
||||
assertThat(result, hasNoStandardOutput());
|
||||
assertThat(result.stderr(), containsString("ERROR: No such job \u2018never_created1\u2019 exists. Perhaps you meant \u2018never_created\u2019?"));
|
||||
assertThat(result.stderr(), containsString("ERROR: No such item ‘never_created1’ exists. Perhaps you meant ‘never_created’?"));
|
||||
}
|
||||
|
||||
@Test public void reloadJobManyShouldSucceed() throws Exception {
|
||||
@@ -182,7 +182,7 @@ public class ReloadJobCommandTest {
|
||||
|
||||
assertThat(result, failedWith(5));
|
||||
assertThat(result, hasNoStandardOutput());
|
||||
assertThat(result.stderr(), containsString("never_created: No such job \u2018never_created\u2019 exists."));
|
||||
assertThat(result.stderr(), containsString("never_created: No such item ‘never_created’ exists."));
|
||||
assertThat(result.stderr(), containsString("ERROR: " + CLICommand.CLI_LISTPARAM_SUMMARY_ERROR_TEXT));
|
||||
|
||||
assertThat(project1.scheduleBuild2(0).get().getLog(), containsString("echo 2"));
|
||||
@@ -208,7 +208,7 @@ public class ReloadJobCommandTest {
|
||||
|
||||
assertThat(result, failedWith(5));
|
||||
assertThat(result, hasNoStandardOutput());
|
||||
assertThat(result.stderr(), containsString("never_created: No such job \u2018never_created\u2019 exists."));
|
||||
assertThat(result.stderr(), containsString("never_created: No such item ‘never_created’ exists."));
|
||||
assertThat(result.stderr(), containsString("ERROR: " + CLICommand.CLI_LISTPARAM_SUMMARY_ERROR_TEXT));
|
||||
|
||||
assertThat(project1.scheduleBuild2(0).get().getLog(), containsString("echo 2"));
|
||||
@@ -234,7 +234,7 @@ public class ReloadJobCommandTest {
|
||||
|
||||
assertThat(result, failedWith(5));
|
||||
assertThat(result, hasNoStandardOutput());
|
||||
assertThat(result.stderr(), containsString("never_created: No such job \u2018never_created\u2019 exists."));
|
||||
assertThat(result.stderr(), containsString("never_created: No such item ‘never_created’ exists."));
|
||||
assertThat(result.stderr(), containsString("ERROR: " + CLICommand.CLI_LISTPARAM_SUMMARY_ERROR_TEXT));
|
||||
|
||||
assertThat(project1.scheduleBuild2(0).get().getLog(), containsString("echo 2"));
|
||||
@@ -260,8 +260,8 @@ public class ReloadJobCommandTest {
|
||||
|
||||
assertThat(result, failedWith(5));
|
||||
assertThat(result, hasNoStandardOutput());
|
||||
assertThat(result.stderr(), containsString("never_created1: No such job \u2018never_created1\u2019 exists."));
|
||||
assertThat(result.stderr(), containsString("never_created2: No such job \u2018never_created2\u2019 exists."));
|
||||
assertThat(result.stderr(), containsString("never_created1: No such item ‘never_created1’ exists."));
|
||||
assertThat(result.stderr(), containsString("never_created2: No such item ‘never_created2’ exists."));
|
||||
assertThat(result.stderr(), containsString("ERROR: " + CLICommand.CLI_LISTPARAM_SUMMARY_ERROR_TEXT));
|
||||
|
||||
assertThat(project1.scheduleBuild2(0).get().getLog(), containsString("echo 2"));
|
||||
|
||||
+2
-2
@@ -54,14 +54,14 @@ import static org.hamcrest.Matchers.not;
|
||||
/**
|
||||
* @author pjanouse
|
||||
*/
|
||||
public class AbstractBuildRangeCommand2Test {
|
||||
public class RunRangeCommand2Test {
|
||||
|
||||
private CLICommandInvoker command;
|
||||
|
||||
@Rule public final JenkinsRule j = new JenkinsRule();
|
||||
|
||||
@Before public void setUp() {
|
||||
command = new CLICommandInvoker(j, new AbstractBuildRangeCommandTest.DummyRangeCommand());
|
||||
command = new CLICommandInvoker(j, new RunRangeCommandTest.DummyRangeCommand());
|
||||
}
|
||||
|
||||
@Test public void dummyRangeShouldFailIfJobNameIsEmptyOnEmptyJenkins() throws Exception {
|
||||
+5
-5
@@ -25,9 +25,9 @@
|
||||
package hudson.cli;
|
||||
|
||||
import hudson.Extension;
|
||||
import hudson.model.AbstractBuild;
|
||||
import hudson.model.FreeStyleProject;
|
||||
import hudson.model.Job;
|
||||
import hudson.model.Run;
|
||||
import jenkins.model.Jenkins;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.ClassRule;
|
||||
@@ -48,7 +48,7 @@ import static org.hamcrest.Matchers.not;
|
||||
/**
|
||||
* @author pjanouse
|
||||
*/
|
||||
public class AbstractBuildRangeCommandTest {
|
||||
public class RunRangeCommandTest {
|
||||
|
||||
private static CLICommandInvoker command = null;
|
||||
private static FreeStyleProject project = null;
|
||||
@@ -977,18 +977,18 @@ public class AbstractBuildRangeCommandTest {
|
||||
}
|
||||
|
||||
@Extension
|
||||
public static class DummyRangeCommand extends AbstractBuildRangeCommand {
|
||||
public static class DummyRangeCommand extends RunRangeCommand {
|
||||
@Override
|
||||
public String getShortDescription() {
|
||||
return "DummyRangeCommand";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int act(List<AbstractBuild<?, ?>> builds) throws IOException {
|
||||
protected int act(List<Run<?, ?>> builds) throws IOException {
|
||||
boolean comma = false;
|
||||
|
||||
stdout.print("Builds: ");
|
||||
for (AbstractBuild build : builds) {
|
||||
for (Run<?, ?> build : builds) {
|
||||
if (comma)
|
||||
stdout.print(",");
|
||||
else
|
||||
Reference in new issue
Block a user