From 5529e90230c8a76da9f9a37ce4d0b5655b65187b Mon Sep 17 00:00:00 2001 From: ajindal-mdx Date: Tue, 28 Oct 2025 14:44:46 +0530 Subject: [PATCH 1/4] Explicit Poll Workspace Implementation --- .../org/jenkinsci/plugins/p4/PerforceScm.java | 37 +++++++++ .../plugins/p4/changes/P4PollRef.java | 79 +++++++++++++++++++ .../plugins/p4/client/ClientHelper.java | 28 +++++++ .../plugins/p4/tagging/TagAction.java | 47 +++++++++++ .../plugins/p4/tasks/CheckoutTask.java | 4 + .../jenkinsci/plugins/p4/tasks/PollTask.java | 20 +++++ .../plugins/p4/workspace/WorkspaceSpec.java | 12 +++ .../ManualWorkspaceImpl/config.jelly | 1 + .../p4/workspace/WorkspaceSpec/config.jelly | 32 ++++++++ .../WorkspaceSpec/help-pollPath.html | 4 + 10 files changed, 264 insertions(+) create mode 100644 src/main/java/org/jenkinsci/plugins/p4/changes/P4PollRef.java create mode 100644 src/main/resources/org/jenkinsci/plugins/p4/workspace/WorkspaceSpec/help-pollPath.html diff --git a/src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java b/src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java index f37752589..8b0abbd1e 100644 --- a/src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java +++ b/src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java @@ -47,8 +47,10 @@ import org.jenkinsci.plugins.p4.changes.P4ChangeSet; import org.jenkinsci.plugins.p4.changes.P4GraphRef; import org.jenkinsci.plugins.p4.changes.P4LabelRef; +import org.jenkinsci.plugins.p4.changes.P4PollRef; import org.jenkinsci.plugins.p4.changes.P4Ref; import org.jenkinsci.plugins.p4.client.ConnectionHelper; +import org.jenkinsci.plugins.p4.client.TempClientHelper; import org.jenkinsci.plugins.p4.credentials.P4BaseCredentials; import org.jenkinsci.plugins.p4.credentials.P4CredentialsImpl; import org.jenkinsci.plugins.p4.credentials.P4InvalidCredentialException; @@ -97,6 +99,7 @@ import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; public class PerforceScm extends SCM { @@ -530,10 +533,13 @@ private List lookForChanges(FilePath buildWorkspace, Workspace ws, Run lastPollPathRefs = TagAction.getLastPollChange(lastRun, listener, syncID); + // Create task PollTask task = new PollTask(credential, lastRun, listener, filter, lastRefs); task.setWorkspace(ws); task.setLimit(pin); + task.setPollRefChanges(lastPollPathRefs); // Execute remote task List changes = buildWorkspace.act(task); @@ -650,10 +656,41 @@ To help lookForChanges() find the correct change to build, sending it the previo task.setIncrementalChanges(changes); } + // Adding all the pollPath and their latest changes in a list + List finalPollPathList = new ArrayList(); + if (ws instanceof ManualWorkspaceImpl) { + String workspacePollpath = ((ManualWorkspaceImpl) ws).getSpec().getPollPath(); + List pollPathList = new ArrayList<>(); + // Extract and clean comma-separated polling paths + if (workspacePollpath != null && !workspacePollpath.isEmpty()) { + pollPathList = Arrays.stream(workspacePollpath.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + } + + if(!pollPathList.isEmpty()) { + try (TempClientHelper p4 = new TempClientHelper(null, credential, listener, null)) { + for (String pollPath : pollPathList) { + // Fetching the latest change for each polling path + List changes = p4.listHaveChangesForPollPath(new P4PollRef(0, pollPath)); + if (!changes.isEmpty()) { + P4Ref latestChange = changes.get(0); + P4PollRef pollRef = new P4PollRef(latestChange.getChange(), pollPath); + finalPollPathList.add(pollRef); + } + } + } catch (Exception ex) { + throw new RuntimeException("Error while processing polling paths: ", ex); + } + } + } + // Add tagging action to build, enabling label support. TagAction tag = new TagAction(run, credential); tag.setWorkspace(ws); tag.setRefChanges(task.getSyncChange()); + tag.setPollPathChanges(finalPollPathList); // JENKINS-37442: Make the log file name available tag.setChangelog(changelogFile); // JENKINS-39107: Make Depot location of Jenkins file available diff --git a/src/main/java/org/jenkinsci/plugins/p4/changes/P4PollRef.java b/src/main/java/org/jenkinsci/plugins/p4/changes/P4PollRef.java new file mode 100644 index 000000000..428f9dcd3 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/p4/changes/P4PollRef.java @@ -0,0 +1,79 @@ +package org.jenkinsci.plugins.p4.changes; + +import com.perforce.p4java.core.IChangelistSummary; +import com.perforce.p4java.core.file.IFileSpec; +import org.jenkinsci.plugins.p4.client.ClientHelper; +import org.jenkinsci.plugins.p4.client.ConnectionHelper; + +import java.util.List; + +public class P4PollRef implements P4Ref { + + private final long change; + private final String pollPath; + + public P4PollRef(long change, String pollPath) { + this.change = change; + this.pollPath = pollPath; + } + + @Override + public P4ChangeEntry getChangeEntry(ClientHelper p4) throws Exception { + P4ChangeEntry cl = new P4ChangeEntry(); + IChangelistSummary summary = p4.getChangeSummary(change); + cl.setChange(p4, summary); + return cl; + } + + @Override + public boolean isLabel() { + return false; + } + + @Override + public boolean isCommit() { + return false; + } + + @Override + public long getChange() { + return change; + } + + public String getPollPath() { + return pollPath; + } + + @Override + public List getFiles(ConnectionHelper p4, int limit) throws Exception { + return p4.getChangeFiles(change, limit); + } + + @Override + public String toString() { + return Long.toString(change); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof P4PollRef) { + P4PollRef ref = (P4PollRef) obj; + if (ref.getPollPath().equals(this.pollPath) && ref.getChange()==this.change) { + return true; + } + } + return false; + } + + @Override + public int compareTo(Object obj) { + if (equals(obj)) { + return 0; + } + if (obj instanceof P4PollRef) { + P4PollRef ref = (P4PollRef) obj; + return (int) (change - ref.getChange()); + } + throw new ClassCastException(); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/p4/client/ClientHelper.java b/src/main/java/org/jenkinsci/plugins/p4/client/ClientHelper.java index b92a2f53a..91af14ea9 100644 --- a/src/main/java/org/jenkinsci/plugins/p4/client/ClientHelper.java +++ b/src/main/java/org/jenkinsci/plugins/p4/client/ClientHelper.java @@ -41,6 +41,7 @@ import org.jenkinsci.plugins.p4.changes.P4ChangeRef; import org.jenkinsci.plugins.p4.changes.P4GraphRef; import org.jenkinsci.plugins.p4.changes.P4LabelRef; +import org.jenkinsci.plugins.p4.changes.P4PollRef; import org.jenkinsci.plugins.p4.changes.P4Ref; import org.jenkinsci.plugins.p4.credentials.P4BaseCredentials; import org.jenkinsci.plugins.p4.populate.AutoCleanImpl; @@ -1388,6 +1389,33 @@ public List listHaveChanges(List fromRefs) throws Exception { return listHaveChanges(path); } + /** + * Fetches a list of changes for a directory poll path (other than client view mapping) + * + * @param from List of from revisions + * @return changelist To Revision + * @throws Exception push up stack + */ + public List listHaveChangesForPollPath(P4PollRef from) throws Exception { + List finalChanges = new ArrayList(); + + if (from.getChange() >= 0) { + String path = from.getPollPath() + "/...@" + from.getChange() + ",now"; + log("P4: Polling with range: " + from + ",now for poll path: " + from.getPollPath()); + + List changes = listChanges(path); + if (changes.size() > 0) { + P4PollRef firstChange = new P4PollRef(changes.get(0).getChange(), from.getPollPath()); + if (!from.equals(firstChange)) { + finalChanges.add(firstChange); + return finalChanges; + } + } + } + + return finalChanges; + } + /** * Fetches a list of changes needed to update the workspace to the specified * limit. The limit could be a Perforce change number, label or counter. diff --git a/src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java b/src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java index edaac9678..6c9820cf3 100644 --- a/src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java +++ b/src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java @@ -10,6 +10,7 @@ import org.jenkinsci.plugins.p4.PerforceScm; import org.jenkinsci.plugins.p4.changes.P4ChangeRef; import org.jenkinsci.plugins.p4.changes.P4LabelRef; +import org.jenkinsci.plugins.p4.changes.P4PollRef; import org.jenkinsci.plugins.p4.changes.P4Ref; import org.jenkinsci.plugins.p4.changes.P4Revision; import org.jenkinsci.plugins.p4.client.ClientHelper; @@ -41,6 +42,7 @@ public class TagAction extends AbstractScmTagAction { private List tags = new ArrayList(); private List refChanges; + private List pollRefChanges; private P4Revision buildChange; private P4Review review; @@ -298,6 +300,36 @@ public static List getLastChange(Run run, TaskListener listener, St return changes; } + public static List getLastPollChange(Run run, TaskListener listener, String syncID) { + List changes = new ArrayList<>(); + + List actions = lastActions(run); + + if (actions == null || syncID == null || syncID.isEmpty()) { + listener.getLogger().println("No previous build found..."); + return changes; + } + + logger.fine(" using syncID: " + syncID); + + boolean found = false; + for (TagAction action : actions) { + if (syncID.equals(action.getSyncID())) { + List pollChanges = action.getPollPathChanges(); + + if (pollChanges != null && !pollChanges.isEmpty()) { + for (P4PollRef poll : pollChanges) { + if (poll.getPollPath() != null && !changes.contains(poll)) { + changes.add(poll); + } + } + } + } + } + + return changes; + } + /** * Find the last action; use this for environment variable as the last action has the latest values. * @@ -353,4 +385,19 @@ public void setJenkinsPath(String jenkinsPath) { public String getJenkinsPath() { return jenkinsPath; } + + public void setPollPathChanges(List finalPollPathList) { + List changes = new ArrayList(); + + for(P4Ref ref : finalPollPathList) { + P4PollRef poll = new P4PollRef(ref.getChange(), ((P4PollRef)ref).getPollPath()); + changes.add(poll); + } + this.pollRefChanges = changes; + } + + public List getPollPathChanges() { + return this.pollRefChanges; + } + } diff --git a/src/main/java/org/jenkinsci/plugins/p4/tasks/CheckoutTask.java b/src/main/java/org/jenkinsci/plugins/p4/tasks/CheckoutTask.java index 3485fc386..61674e30e 100644 --- a/src/main/java/org/jenkinsci/plugins/p4/tasks/CheckoutTask.java +++ b/src/main/java/org/jenkinsci/plugins/p4/tasks/CheckoutTask.java @@ -14,6 +14,7 @@ import org.jenkinsci.plugins.p4.changes.P4ChangeEntry; import org.jenkinsci.plugins.p4.changes.P4ChangeRef; import org.jenkinsci.plugins.p4.changes.P4LabelRef; +import org.jenkinsci.plugins.p4.changes.P4PollRef; import org.jenkinsci.plugins.p4.changes.P4Ref; import org.jenkinsci.plugins.p4.client.ClientHelper; import org.jenkinsci.plugins.p4.console.P4Logging; @@ -315,6 +316,9 @@ public List getChangesFull(List lastRefs) { } } else { // add classic changes + if(build instanceof P4PollRef) { + continue; + } List changes = p4.listChanges(lastRefs, build); for (P4Ref change : changes) { P4ChangeEntry cl = change.getChangeEntry(p4); diff --git a/src/main/java/org/jenkinsci/plugins/p4/tasks/PollTask.java b/src/main/java/org/jenkinsci/plugins/p4/tasks/PollTask.java index 7923602a5..dcef90329 100644 --- a/src/main/java/org/jenkinsci/plugins/p4/tasks/PollTask.java +++ b/src/main/java/org/jenkinsci/plugins/p4/tasks/PollTask.java @@ -12,6 +12,7 @@ import jenkins.security.Roles; import org.jenkinsci.plugins.p4.changes.P4ChangeRef; import org.jenkinsci.plugins.p4.changes.P4LabelRef; +import org.jenkinsci.plugins.p4.changes.P4PollRef; import org.jenkinsci.plugins.p4.changes.P4Ref; import org.jenkinsci.plugins.p4.client.ClientHelper; import org.jenkinsci.plugins.p4.filters.Filter; @@ -37,6 +38,7 @@ public class PollTask extends AbstractTask implements FileCallable>, private final List filter; private final List lastRefs; + private List lastPollRefs; private String pin; @@ -44,6 +46,7 @@ public PollTask(String credential, Run run, TaskListener listener, List pollChanges = new ArrayList(); + + for (P4PollRef ref : lastPollRefs) { + pollChanges = p4.listHaveChangesForPollPath(ref); + changes.addAll(pollChanges); + } + } return changes; } @@ -101,6 +113,14 @@ public void setLimit(String expandedPin) { pin = expandedPin; } + public void setPollRefChanges(List pollRefs) { + lastPollRefs = pollRefs; + } + + public List getPollRefChanges() { + return lastPollRefs; + } + /** * Returns true if change should be filtered * diff --git a/src/main/java/org/jenkinsci/plugins/p4/workspace/WorkspaceSpec.java b/src/main/java/org/jenkinsci/plugins/p4/workspace/WorkspaceSpec.java index f780bfaed..8e8e9f8d9 100644 --- a/src/main/java/org/jenkinsci/plugins/p4/workspace/WorkspaceSpec.java +++ b/src/main/java/org/jenkinsci/plugins/p4/workspace/WorkspaceSpec.java @@ -36,6 +36,16 @@ public class WorkspaceSpec extends AbstractDescribableImpl implem private final String serverID; private final boolean backup; + private String pollPath; + + public String getPollPath() { + return pollPath; + } + + @DataBoundSetter + public void setPollPath(String pollPath) { + this.pollPath = pollPath; + } public String getStreamName() { return streamName; @@ -96,6 +106,7 @@ public WorkspaceSpec(boolean allwrite, boolean clobber, boolean compress, this.type = type; this.serverID = serverID; this.backup = backup; + this.pollPath = getPollPath(); } // Default setup for Classic Workspace @@ -114,6 +125,7 @@ public WorkspaceSpec(String view, String changeView) { this.type = null; this.serverID = null; this.backup = true; + this.pollPath = getPollPath(); } @Deprecated diff --git a/src/main/resources/org/jenkinsci/plugins/p4/workspace/ManualWorkspaceImpl/config.jelly b/src/main/resources/org/jenkinsci/plugins/p4/workspace/ManualWorkspaceImpl/config.jelly index 9b5887d1a..8e49462b7 100644 --- a/src/main/resources/org/jenkinsci/plugins/p4/workspace/ManualWorkspaceImpl/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/p4/workspace/ManualWorkspaceImpl/config.jelly @@ -40,6 +40,7 @@ document.getElementById("id.stream").value = json.stream; document.getElementById("id.line").value = json.line; document.getElementById("id.view").value = json.view; + document.getElementById("id.pollPath").value = json.pollPath; }); } diff --git a/src/main/resources/org/jenkinsci/plugins/p4/workspace/WorkspaceSpec/config.jelly b/src/main/resources/org/jenkinsci/plugins/p4/workspace/WorkspaceSpec/config.jelly index 49050b51a..cdd423785 100644 --- a/src/main/resources/org/jenkinsci/plugins/p4/workspace/WorkspaceSpec/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/p4/workspace/WorkspaceSpec/config.jelly @@ -44,4 +44,36 @@ + + +
+
+ + + \ No newline at end of file diff --git a/src/main/resources/org/jenkinsci/plugins/p4/workspace/WorkspaceSpec/help-pollPath.html b/src/main/resources/org/jenkinsci/plugins/p4/workspace/WorkspaceSpec/help-pollPath.html new file mode 100644 index 000000000..8ef0e461e --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/p4/workspace/WorkspaceSpec/help-pollPath.html @@ -0,0 +1,4 @@ +
+ Polling Path +

Polling Path refers to specifying additional paths outside the client view mapping for polling. Once configured and saved, build the project to apply changes. These paths are registered with the project, and any detected changes during polling will automatically trigger a build. Only comma-separated values are allowed, with a maximum of 10 entries.

+
\ No newline at end of file From 2dd95c0a35fb461edd51e88ddf174451bed79f5a Mon Sep 17 00:00:00 2001 From: ajindal-mdx Date: Thu, 30 Oct 2025 16:15:38 +0530 Subject: [PATCH 2/4] limiting the polling paths to 10 --- src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java b/src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java index 8b0abbd1e..f115160dd 100644 --- a/src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java +++ b/src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java @@ -670,6 +670,7 @@ To help lookForChanges() find the correct change to build, sending it the previo } if(!pollPathList.isEmpty()) { + pollPathList = new ArrayList<>(pollPathList.subList(0, Math.min(10, pollPathList.size()))); try (TempClientHelper p4 = new TempClientHelper(null, credential, listener, null)) { for (String pollPath : pollPathList) { // Fetching the latest change for each polling path From e3da6938ad4afa37dfd0a1d33d0f681953643a6e Mon Sep 17 00:00:00 2001 From: ajindal-mdx Date: Wed, 5 Nov 2025 17:13:20 +0530 Subject: [PATCH 3/4] Adding support for /... --- .../org/jenkinsci/plugins/p4/PerforceScm.java | 9 ++-- .../plugins/p4/client/ClientHelper.java | 47 +++++++++++-------- .../plugins/p4/tagging/TagAction.java | 6 ++- .../jenkinsci/plugins/p4/tasks/PollTask.java | 6 ++- 4 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java b/src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java index f115160dd..f8ccb0c30 100644 --- a/src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java +++ b/src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java @@ -673,12 +673,9 @@ To help lookForChanges() find the correct change to build, sending it the previo pollPathList = new ArrayList<>(pollPathList.subList(0, Math.min(10, pollPathList.size()))); try (TempClientHelper p4 = new TempClientHelper(null, credential, listener, null)) { for (String pollPath : pollPathList) { - // Fetching the latest change for each polling path - List changes = p4.listHaveChangesForPollPath(new P4PollRef(0, pollPath)); - if (!changes.isEmpty()) { - P4Ref latestChange = changes.get(0); - P4PollRef pollRef = new P4PollRef(latestChange.getChange(), pollPath); - finalPollPathList.add(pollRef); + P4Ref changes = p4.getLatestChangeForPollPath(new P4PollRef(0, pollPath)); + if(changes != null) { + finalPollPathList.add(changes); } } } catch (Exception ex) { diff --git a/src/main/java/org/jenkinsci/plugins/p4/client/ClientHelper.java b/src/main/java/org/jenkinsci/plugins/p4/client/ClientHelper.java index 91af14ea9..b81e0ec94 100644 --- a/src/main/java/org/jenkinsci/plugins/p4/client/ClientHelper.java +++ b/src/main/java/org/jenkinsci/plugins/p4/client/ClientHelper.java @@ -1390,30 +1390,37 @@ public List listHaveChanges(List fromRefs) throws Exception { } /** - * Fetches a list of changes for a directory poll path (other than client view mapping) + * Retrieves the latest Perforce changelist for a given polling path since a specified changelist number. + * If no changes exist or the input is invalid, the method returns {@code null}. * - * @param from List of from revisions - * @return changelist To Revision - * @throws Exception push up stack + * @param from the reference containing the polling path and starting changelist number; + * @return a {@link P4PollRef} representing the latest change found for the given path, + * or {@code null} if no changes are found or if the input reference is invalid. + * @throws Exception if an error occurs while retrieving the list of changes from Perforce. */ - public List listHaveChangesForPollPath(P4PollRef from) throws Exception { - List finalChanges = new ArrayList(); - - if (from.getChange() >= 0) { - String path = from.getPollPath() + "/...@" + from.getChange() + ",now"; - log("P4: Polling with range: " + from + ",now for poll path: " + from.getPollPath()); - - List changes = listChanges(path); - if (changes.size() > 0) { - P4PollRef firstChange = new P4PollRef(changes.get(0).getChange(), from.getPollPath()); - if (!from.equals(firstChange)) { - finalChanges.add(firstChange); - return finalChanges; - } - } + + public P4Ref getLatestChangeForPollPath(P4PollRef from) throws Exception { + if(from == null && from.getChange()<0) { + return null; + } + + String path; + if(from.getPollPath().endsWith("/...")) + path = from.getPollPath() + "@" + from.getChange() + ",now"; + else + path = from.getPollPath() + "/...@" + from.getChange() + ",now"; + + List changes = listChanges(path); + if (changes.isEmpty()) { + return null; + } + + P4Ref finalChange = new P4PollRef(changes.get(0).getChange(), from.getPollPath()); + if(from.equals(finalChange)) { + return null; } - return finalChanges; + return finalChange; } /** diff --git a/src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java b/src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java index 6c9820cf3..8db00a1af 100644 --- a/src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java +++ b/src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java @@ -390,8 +390,10 @@ public void setPollPathChanges(List finalPollPathList) { List changes = new ArrayList(); for(P4Ref ref : finalPollPathList) { - P4PollRef poll = new P4PollRef(ref.getChange(), ((P4PollRef)ref).getPollPath()); - changes.add(poll); + if(ref != null) { + P4PollRef poll = new P4PollRef(ref.getChange(), ((P4PollRef)ref).getPollPath()); + changes.add(poll); + } } this.pollRefChanges = changes; } diff --git a/src/main/java/org/jenkinsci/plugins/p4/tasks/PollTask.java b/src/main/java/org/jenkinsci/plugins/p4/tasks/PollTask.java index dcef90329..9252546e1 100644 --- a/src/main/java/org/jenkinsci/plugins/p4/tasks/PollTask.java +++ b/src/main/java/org/jenkinsci/plugins/p4/tasks/PollTask.java @@ -102,8 +102,10 @@ public Object task(ClientHelper p4) throws Exception { List pollChanges = new ArrayList(); for (P4PollRef ref : lastPollRefs) { - pollChanges = p4.listHaveChangesForPollPath(ref); - changes.addAll(pollChanges); + P4Ref changeref = p4.getLatestChangeForPollPath(ref); + if (changeref != null) { + changes.add(changeref); + } } } return changes; From a22f6eb547865fcd2bd42fe0c710ed414b48ba93 Mon Sep 17 00:00:00 2001 From: ajindal-mdx Date: Tue, 11 Nov 2025 15:48:47 +0530 Subject: [PATCH 4/4] Code Restructuring --- .../org/jenkinsci/plugins/p4/PerforceScm.java | 32 +--------- .../plugins/p4/client/ClientHelper.java | 45 ++------------ .../plugins/p4/client/ConnectionHelper.java | 37 ++++++++++- .../plugins/p4/tagging/TagAction.java | 47 +++++++++----- .../plugins/p4/tasks/CheckoutTask.java | 62 ++++++++++++++++++- .../jenkinsci/plugins/p4/tasks/PollTask.java | 44 +++++++------ .../plugins/p4/workspace/WorkspaceSpec.java | 2 - 7 files changed, 152 insertions(+), 117 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java b/src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java index f8ccb0c30..85278e43a 100644 --- a/src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java +++ b/src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java @@ -50,7 +50,6 @@ import org.jenkinsci.plugins.p4.changes.P4PollRef; import org.jenkinsci.plugins.p4.changes.P4Ref; import org.jenkinsci.plugins.p4.client.ConnectionHelper; -import org.jenkinsci.plugins.p4.client.TempClientHelper; import org.jenkinsci.plugins.p4.credentials.P4BaseCredentials; import org.jenkinsci.plugins.p4.credentials.P4CredentialsImpl; import org.jenkinsci.plugins.p4.credentials.P4InvalidCredentialException; @@ -99,7 +98,6 @@ import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; public class PerforceScm extends SCM { @@ -656,39 +654,11 @@ To help lookForChanges() find the correct change to build, sending it the previo task.setIncrementalChanges(changes); } - // Adding all the pollPath and their latest changes in a list - List finalPollPathList = new ArrayList(); - if (ws instanceof ManualWorkspaceImpl) { - String workspacePollpath = ((ManualWorkspaceImpl) ws).getSpec().getPollPath(); - List pollPathList = new ArrayList<>(); - // Extract and clean comma-separated polling paths - if (workspacePollpath != null && !workspacePollpath.isEmpty()) { - pollPathList = Arrays.stream(workspacePollpath.split(",")) - .map(String::trim) - .filter(s -> !s.isEmpty()) - .collect(Collectors.toList()); - } - - if(!pollPathList.isEmpty()) { - pollPathList = new ArrayList<>(pollPathList.subList(0, Math.min(10, pollPathList.size()))); - try (TempClientHelper p4 = new TempClientHelper(null, credential, listener, null)) { - for (String pollPath : pollPathList) { - P4Ref changes = p4.getLatestChangeForPollPath(new P4PollRef(0, pollPath)); - if(changes != null) { - finalPollPathList.add(changes); - } - } - } catch (Exception ex) { - throw new RuntimeException("Error while processing polling paths: ", ex); - } - } - } - // Add tagging action to build, enabling label support. TagAction tag = new TagAction(run, credential); tag.setWorkspace(ws); tag.setRefChanges(task.getSyncChange()); - tag.setPollPathChanges(finalPollPathList); + tag.setPollPathChanges(task.resolvePollPathsToLatestChanges()); // JENKINS-37442: Make the log file name available tag.setChangelog(changelogFile); // JENKINS-39107: Make Depot location of Jenkins file available diff --git a/src/main/java/org/jenkinsci/plugins/p4/client/ClientHelper.java b/src/main/java/org/jenkinsci/plugins/p4/client/ClientHelper.java index b81e0ec94..04453370e 100644 --- a/src/main/java/org/jenkinsci/plugins/p4/client/ClientHelper.java +++ b/src/main/java/org/jenkinsci/plugins/p4/client/ClientHelper.java @@ -41,7 +41,6 @@ import org.jenkinsci.plugins.p4.changes.P4ChangeRef; import org.jenkinsci.plugins.p4.changes.P4GraphRef; import org.jenkinsci.plugins.p4.changes.P4LabelRef; -import org.jenkinsci.plugins.p4.changes.P4PollRef; import org.jenkinsci.plugins.p4.changes.P4Ref; import org.jenkinsci.plugins.p4.credentials.P4BaseCredentials; import org.jenkinsci.plugins.p4.populate.AutoCleanImpl; @@ -1292,12 +1291,12 @@ public List listChanges(List fromRefs, P4Ref to) throws Exception // return empty array, if from and to are equal, or Perforce will report // a change if (from.equals(to)) { - return new ArrayList(); + return new ArrayList<>(); } // JENKINS-68516: skip changelist calculation if maxChanges=0. if (getMaxChangeLimit() <= 0) { - return new ArrayList(); + return new ArrayList<>(); } String ws = "//" + iclient.getName() + "/...@" + from + "," + to; @@ -1347,7 +1346,7 @@ public List listChanges() throws Exception { } private List listChanges(String ws) throws Exception { - List list = new ArrayList(); + List list = new ArrayList<>(); GetChangelistsOptions opts = new GetChangelistsOptions(); opts.setMaxMostRecent(getMaxChangeLimit()); @@ -1389,40 +1388,6 @@ public List listHaveChanges(List fromRefs) throws Exception { return listHaveChanges(path); } - /** - * Retrieves the latest Perforce changelist for a given polling path since a specified changelist number. - * If no changes exist or the input is invalid, the method returns {@code null}. - * - * @param from the reference containing the polling path and starting changelist number; - * @return a {@link P4PollRef} representing the latest change found for the given path, - * or {@code null} if no changes are found or if the input reference is invalid. - * @throws Exception if an error occurs while retrieving the list of changes from Perforce. - */ - - public P4Ref getLatestChangeForPollPath(P4PollRef from) throws Exception { - if(from == null && from.getChange()<0) { - return null; - } - - String path; - if(from.getPollPath().endsWith("/...")) - path = from.getPollPath() + "@" + from.getChange() + ",now"; - else - path = from.getPollPath() + "/...@" + from.getChange() + ",now"; - - List changes = listChanges(path); - if (changes.isEmpty()) { - return null; - } - - P4Ref finalChange = new P4PollRef(changes.get(0).getChange(), from.getPollPath()); - if(from.equals(finalChange)) { - return null; - } - - return finalChange; - } - /** * Fetches a list of changes needed to update the workspace to the specified * limit. The limit could be a Perforce change number, label or counter. @@ -1441,7 +1406,7 @@ public List listHaveChanges(List fromRefs, P4Ref changeLimit) thro } // return empty array, if from and changeLimit are equal, or Perforce will report a change if (from.equals(changeLimit)) { - return new ArrayList(); + return new ArrayList<>(); } if (from.getChange() > 0) { @@ -1457,7 +1422,7 @@ public List listHaveChanges(List fromRefs, P4Ref changeLimit) thro private List listHaveChanges(String fileSpec) throws Exception { log("P4: Polling with cstat: " + fileSpec); - List haveChanges = new ArrayList(); + List haveChanges = new ArrayList<>(); Map[] map; map = getConnection().execMapCmd("cstat", new String[]{fileSpec}, null); diff --git a/src/main/java/org/jenkinsci/plugins/p4/client/ConnectionHelper.java b/src/main/java/org/jenkinsci/plugins/p4/client/ConnectionHelper.java index d8d508ab5..e96c7627b 100644 --- a/src/main/java/org/jenkinsci/plugins/p4/client/ConnectionHelper.java +++ b/src/main/java/org/jenkinsci/plugins/p4/client/ConnectionHelper.java @@ -35,7 +35,9 @@ import org.jenkinsci.plugins.p4.PerforceScm; import org.jenkinsci.plugins.p4.changes.P4GraphRef; import org.jenkinsci.plugins.p4.changes.P4LabelRef; +import org.jenkinsci.plugins.p4.changes.P4PollRef; import org.jenkinsci.plugins.p4.changes.P4Ref; +import org.jenkinsci.plugins.p4.changes.P4ChangeRef; import org.jenkinsci.plugins.p4.credentials.P4BaseCredentials; import java.io.IOException; @@ -43,6 +45,7 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; +import java.util.Collections; import java.util.logging.Level; import java.util.logging.Logger; @@ -607,4 +610,36 @@ public long getHeadLimit() { public void close() throws Exception { disconnect(); } -} + + /** + * Retrieves the latest Perforce changelist for a given polling path since a specified changelist number. + * If no changes exist or the input is invalid, the method returns {@code null}. + * + * @param from the reference containing the polling path and starting changelist number; + * @return a {@link P4PollRef} representing the latest change found for the given path, + * or {@code null} if no changes are found or if the input reference is invalid. + * @throws Exception if an error occurs while retrieving the list of changes from Perforce. + */ + + public P4PollRef getLatestChangeForPollPath(P4PollRef from) throws Exception { + if (from == null || from.getChange() < 0) { + return null; + } + + String pollPath = from.getPollPath(); + String path = pollPath.endsWith("/...") + ? pollPath + "@" + from.getChange() + ",now" + : pollPath + "/...@" + from.getChange() + ",now"; + + List spec = FileSpecBuilder.makeFileSpecList(path); + GetChangelistsOptions opts = new GetChangelistsOptions(); + opts.setMaxMostRecent(1); + List changes = getConnection().getChangelists(spec, opts); + if (changes.isEmpty()) { + return null; + } + + P4PollRef finalChange = new P4PollRef(changes.get(0).getId(), pollPath); + return from.equals(finalChange) ? null : finalChange; + } +} \ No newline at end of file diff --git a/src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java b/src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java index 8db00a1af..fe0cdc2db 100644 --- a/src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java +++ b/src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java @@ -30,7 +30,9 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -42,7 +44,7 @@ public class TagAction extends AbstractScmTagAction { private List tags = new ArrayList(); private List refChanges; - private List pollRefChanges; + private List pollRefChanges = new ArrayList<>(); private P4Revision buildChange; private P4Review review; @@ -301,33 +303,44 @@ public static List getLastChange(Run run, TaskListener listener, St } public static List getLastPollChange(Run run, TaskListener listener, String syncID) { - List changes = new ArrayList<>(); + // Use LinkedHashSet to preserve insertion order while deduplicating by + // P4PollRef.equals()/hashCode(). + Set result = new LinkedHashSet<>(); List actions = lastActions(run); - if (actions == null || syncID == null || syncID.isEmpty()) { listener.getLogger().println("No previous build found..."); - return changes; + return new ArrayList<>(); } logger.fine(" using syncID: " + syncID); - boolean found = false; for (TagAction action : actions) { - if (syncID.equals(action.getSyncID())) { - List pollChanges = action.getPollPathChanges(); - - if (pollChanges != null && !pollChanges.isEmpty()) { - for (P4PollRef poll : pollChanges) { - if (poll.getPollPath() != null && !changes.contains(poll)) { - changes.add(poll); - } - } + if (!syncID.equals(action.getSyncID())) { + continue; + } + + List pollChanges = action.getPollPathChanges(); + if (pollChanges == null || pollChanges.isEmpty()) { + continue; + } + + for (P4PollRef poll : pollChanges) { + if (poll == null) { + continue; + } + String path = poll.getPollPath(); + if (path == null) { + continue; + } + // LinkedHashSet.add will ignore duplicates based on equals/hashCode + if (result.add(poll)) { + listener.getLogger().println("Found last poll change " + poll.toString() + " on syncID " + syncID + " path " + path); } } } - return changes; + return new ArrayList<>(result); } /** @@ -386,8 +399,8 @@ public String getJenkinsPath() { return jenkinsPath; } - public void setPollPathChanges(List finalPollPathList) { - List changes = new ArrayList(); + public void setPollPathChanges(List finalPollPathList) { + List changes = new ArrayList<>(); for(P4Ref ref : finalPollPathList) { if(ref != null) { diff --git a/src/main/java/org/jenkinsci/plugins/p4/tasks/CheckoutTask.java b/src/main/java/org/jenkinsci/plugins/p4/tasks/CheckoutTask.java index 61674e30e..76f8f6c34 100644 --- a/src/main/java/org/jenkinsci/plugins/p4/tasks/CheckoutTask.java +++ b/src/main/java/org/jenkinsci/plugins/p4/tasks/CheckoutTask.java @@ -17,11 +17,13 @@ import org.jenkinsci.plugins.p4.changes.P4PollRef; import org.jenkinsci.plugins.p4.changes.P4Ref; import org.jenkinsci.plugins.p4.client.ClientHelper; +import org.jenkinsci.plugins.p4.client.ConnectionHelper; import org.jenkinsci.plugins.p4.console.P4Logging; import org.jenkinsci.plugins.p4.populate.AutoCleanImpl; import org.jenkinsci.plugins.p4.populate.Populate; import org.jenkinsci.plugins.p4.review.ReviewProp; import org.jenkinsci.plugins.p4.workspace.Expand; +import org.jenkinsci.plugins.p4.workspace.ManualWorkspaceImpl; import org.jenkinsci.plugins.p4.workspace.Workspace; import org.jenkinsci.remoting.RoleChecker; import org.jenkinsci.remoting.RoleSensitive; @@ -30,8 +32,10 @@ import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.logging.Logger; +import java.util.stream.Collectors; public class CheckoutTask extends AbstractTask implements FileCallable, Serializable { @@ -316,9 +320,6 @@ public List getChangesFull(List lastRefs) { } } else { // add classic changes - if(build instanceof P4PollRef) { - continue; - } List changes = p4.listChanges(lastRefs, build); for (P4Ref change : changes) { P4ChangeEntry cl = change.getChangeEntry(p4); @@ -391,4 +392,59 @@ public long getReview() { public void checkRoles(RoleChecker checker) throws SecurityException { checker.check((RoleSensitive) this, Roles.SLAVE); } + + + /** + * Resolve workspace-defined poll paths to their latest Perforce changes. + * + *

This method: + * - Only runs when the configured workspace is a {@link ManualWorkspaceImpl}. + * - Reads the comma-separated poll paths from the workspace spec, trims entries + * and ignores empty values. + * - For up to {@code MAX_PATHS_TO_CHECK} poll paths, queries Perforce for the latest + * changelist for each poll path using {@link ConnectionHelper#getLatestChangeForPollPath(P4PollRef)}. + * - Returns a list of non-null {@link P4Ref} objects (one per path with a found change) + * in the same order the paths were specified. + * + * @return list of latest per-path {@code P4PollRef} results (empty if none) + * @throws RuntimeException if an error occurs while querying Perforce (current behavior) + */ + public List resolvePollPathsToLatestChanges() { + final int MAX_PATHS_TO_CHECK = 10; + + List results = new ArrayList<>(); + Workspace ws = getWorkspace(); + + if (!(ws instanceof ManualWorkspaceImpl)) { + return results; + } + + String pollSpec = ((ManualWorkspaceImpl) ws).getSpec().getPollPath(); + if (pollSpec == null || pollSpec.trim().isEmpty()) { + return results; + } + + List paths = Arrays.stream(pollSpec.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + + if (paths.isEmpty()) { + return results; + } + + try (ConnectionHelper p4 = new ConnectionHelper(getCredential(), getListener())) { + int limit = Math.min(MAX_PATHS_TO_CHECK, paths.size()); + for (int i = 0; i < limit; i++) { + P4PollRef ref = p4.getLatestChangeForPollPath(new P4PollRef(0, paths.get(i))); + if (ref != null) { + results.add(ref); + } + } + } catch (Exception ex) { + throw new RuntimeException("Error while processing polling paths", ex); + } + + return results; + } } diff --git a/src/main/java/org/jenkinsci/plugins/p4/tasks/PollTask.java b/src/main/java/org/jenkinsci/plugins/p4/tasks/PollTask.java index 9252546e1..484c7d5a0 100644 --- a/src/main/java/org/jenkinsci/plugins/p4/tasks/PollTask.java +++ b/src/main/java/org/jenkinsci/plugins/p4/tasks/PollTask.java @@ -38,7 +38,7 @@ public class PollTask extends AbstractTask implements FileCallable>, private final List filter; private final List lastRefs; - private List lastPollRefs; + private List lastPollRefs = new ArrayList<>(); private String pin; @@ -46,7 +46,6 @@ public PollTask(String credential, Run run, TaskListener listener, List remainder = new ArrayList(); - for (P4Ref c : changes) { - long change = c.getChange(); - if (change > 0) { - Changelist changelist = p4.getChange(change); - // add unfiltered changes to remainder list - if (!filterChange(changelist, filter)) { - remainder.add(new P4ChangeRef(changelist.getId())); - p4.log("... found change: " + changelist.getId()); - } - } - } - changes = remainder; - // Poll Graph commit changes if (p4.checkVersion(20171)) { List repos = p4.listRepos(); @@ -99,15 +83,29 @@ public Object task(ClientHelper p4) throws Exception { // Poll polling path changes if (changes.isEmpty() && !lastPollRefs.isEmpty()) { - List pollChanges = new ArrayList(); - for (P4PollRef ref : lastPollRefs) { - P4Ref changeref = p4.getLatestChangeForPollPath(ref); - if (changeref != null) { - changes.add(changeref); + P4PollRef changeRef = p4.getLatestChangeForPollPath(ref); + if (changeRef != null) { + changes.add(changeRef); } } } + + // filter changes... + List remainder = new ArrayList<>(); + for (P4Ref c : changes) { + long change = c.getChange(); + if (change > 0) { + Changelist changelist = p4.getChange(change); + // add unfiltered changes to remainder list + if (!filterChange(changelist, filter)) { + remainder.add(new P4ChangeRef(changelist.getId())); + p4.log("... found change: " + changelist.getId()); + } + } + } + changes = remainder; + return changes; } @@ -116,7 +114,7 @@ public void setLimit(String expandedPin) { } public void setPollRefChanges(List pollRefs) { - lastPollRefs = pollRefs; + this.lastPollRefs = pollRefs; } public List getPollRefChanges() { diff --git a/src/main/java/org/jenkinsci/plugins/p4/workspace/WorkspaceSpec.java b/src/main/java/org/jenkinsci/plugins/p4/workspace/WorkspaceSpec.java index 8e8e9f8d9..34ef2670d 100644 --- a/src/main/java/org/jenkinsci/plugins/p4/workspace/WorkspaceSpec.java +++ b/src/main/java/org/jenkinsci/plugins/p4/workspace/WorkspaceSpec.java @@ -106,7 +106,6 @@ public WorkspaceSpec(boolean allwrite, boolean clobber, boolean compress, this.type = type; this.serverID = serverID; this.backup = backup; - this.pollPath = getPollPath(); } // Default setup for Classic Workspace @@ -125,7 +124,6 @@ public WorkspaceSpec(String view, String changeView) { this.type = null; this.serverID = null; this.backup = true; - this.pollPath = getPollPath(); } @Deprecated