Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/main/java/org/jenkinsci/plugins/p4/PerforceScm.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
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.credentials.P4BaseCredentials;
Expand Down Expand Up @@ -530,10 +531,13 @@ private List<P4Ref> lookForChanges(FilePath buildWorkspace, Workspace ws, Run<?,
return null;
}

List<P4PollRef> 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<P4Ref> changes = buildWorkspace.act(task);
Expand Down Expand Up @@ -654,6 +658,7 @@ To help lookForChanges() find the correct change to build, sending it the previo
TagAction tag = new TagAction(run, credential);
tag.setWorkspace(ws);
tag.setRefChanges(task.getSyncChange());
tag.setPollPathChanges(task.resolvePollPathsToLatestChanges());
// JENKINS-37442: Make the log file name available
tag.setChangelog(changelogFile);
// JENKINS-39107: Make Depot location of Jenkins file available
Expand Down
79 changes: 79 additions & 0 deletions src/main/java/org/jenkinsci/plugins/p4/changes/P4PollRef.java
Original file line number Diff line number Diff line change
@@ -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<IFileSpec> 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;

Check warning on line 65 in src/main/java/org/jenkinsci/plugins/p4/changes/P4PollRef.java

View check run for this annotation

ci.jenkins.io / SpotBugs

HE_EQUALS_USE_HASHCODE

HIGH: org.jenkinsci.plugins.p4.changes.P4PollRef defines equals and uses Object.hashCode()
Raw output
<p> This class overrides <code>equals(Object)</code>, but does not override <code>hashCode()</code>, and inherits the implementation of <code>hashCode()</code> from <code>java.lang.Object</code> (which returns the identity hash code, an arbitrary value assigned to the object by the VM).&nbsp; Therefore, the class is very likely to violate the invariant that equal objects must have equal hashcodes.</p> <p>If you don't think instances of this class will ever be inserted into a HashMap/HashTable, the recommended <code>hashCode</code> implementation to use is:</p> <pre><code>public int hashCode() { assert false : "hashCode not designed"; return 42; // any arbitrary constant will do } </code></pre>
}

@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();

Check warning on line 77 in src/main/java/org/jenkinsci/plugins/p4/changes/P4PollRef.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 15-77 are not covered by tests
}
}
10 changes: 5 additions & 5 deletions src/main/java/org/jenkinsci/plugins/p4/client/ClientHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@
private void syncFiles(String revisions, Populate populate) throws Exception {

// set MODTIME if populate options is used only required before 15.1
if( (populate instanceof AutoCleanImpl) && ((AutoCleanImpl) populate).isModtime() && !checkVersion(20151) ) {

Check warning on line 411 in src/main/java/org/jenkinsci/plugins/p4/client/ClientHelper.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 411 is only partially covered, one branch is missing
IClientOptions options = iclient.getOptions();
if (!options.isModtime()) {
options.setModtime(true);
Expand Down Expand Up @@ -610,7 +610,7 @@
}

// set MODTIME if populate options is used and server supports flag
if ( (populate instanceof AutoCleanImpl) && ((AutoCleanImpl) populate).isModtime() ) {

Check warning on line 613 in src/main/java/org/jenkinsci/plugins/p4/client/ClientHelper.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 613 is only partially covered, one branch is missing
if (checkVersion(20141)) {
cleanOpts.setCheckModTime(true);
} else {
Expand Down Expand Up @@ -1291,12 +1291,12 @@
// return empty array, if from and to are equal, or Perforce will report
// a change
if (from.equals(to)) {
return new ArrayList<P4Ref>();
return new ArrayList<>();
}

// JENKINS-68516: skip changelist calculation if maxChanges=0.
if (getMaxChangeLimit() <= 0) {
return new ArrayList<P4Ref>();
return new ArrayList<>();

Check warning on line 1299 in src/main/java/org/jenkinsci/plugins/p4/client/ClientHelper.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 1299 is not covered by tests
}

String ws = "//" + iclient.getName() + "/...@" + from + "," + to;
Expand Down Expand Up @@ -1346,7 +1346,7 @@
}

private List<P4Ref> listChanges(String ws) throws Exception {
List<P4Ref> list = new ArrayList<P4Ref>();
List<P4Ref> list = new ArrayList<>();
GetChangelistsOptions opts = new GetChangelistsOptions();
opts.setMaxMostRecent(getMaxChangeLimit());

Expand Down Expand Up @@ -1406,23 +1406,23 @@
}
// return empty array, if from and changeLimit are equal, or Perforce will report a change
if (from.equals(changeLimit)) {
return new ArrayList<P4Ref>();
return new ArrayList<>();
}

if (from.getChange() > 0) {
log("P4: Polling with range: " + from + "," + changeLimit);
return listChanges(fromRefs, changeLimit);
}

String path = "//" + iclient.getName() + "/...";
String fileSpec = path + "@" + changeLimit;
return listHaveChanges(fileSpec);
}

private List<P4Ref> listHaveChanges(String fileSpec) throws Exception {
log("P4: Polling with cstat: " + fileSpec);

List<P4Ref> haveChanges = new ArrayList<P4Ref>();
List<P4Ref> haveChanges = new ArrayList<>();

Check warning on line 1425 in src/main/java/org/jenkinsci/plugins/p4/client/ClientHelper.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 1409-1425 are not covered by tests
Map<String, Object>[] map;
map = getConnection().execMapCmd("cstat", new String[]{fileSpec}, null);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,17 @@
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;
import java.util.ArrayList;
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;

Expand Down Expand Up @@ -607,4 +610,36 @@
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<IFileSpec> spec = FileSpecBuilder.makeFileSpecList(path);
GetChangelistsOptions opts = new GetChangelistsOptions();
opts.setMaxMostRecent(1);
List<IChangelistSummary> 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;

Check warning on line 643 in src/main/java/org/jenkinsci/plugins/p4/client/ConnectionHelper.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 625-643 are not covered by tests
}
}
62 changes: 62 additions & 0 deletions src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,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;

Expand All @@ -41,6 +44,7 @@
private List<String> tags = new ArrayList<String>();

private List<P4Ref> refChanges;
private List<P4PollRef> pollRefChanges = new ArrayList<>();

private P4Revision buildChange;
private P4Review review;
Expand Down Expand Up @@ -298,6 +302,47 @@
return changes;
}

public static List<P4PollRef> getLastPollChange(Run<?, ?> run, TaskListener listener, String syncID) {
// Use LinkedHashSet to preserve insertion order while deduplicating by
// P4PollRef.equals()/hashCode().
Set<P4PollRef> result = new LinkedHashSet<>();

List<TagAction> actions = lastActions(run);
if (actions == null || syncID == null || syncID.isEmpty()) {

Check warning on line 311 in src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 311 is only partially covered, 3 branches are missing
listener.getLogger().println("No previous build found...");
return new ArrayList<>();

Check warning on line 313 in src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 312-313 are not covered by tests
}

logger.fine(" using syncID: " + syncID);

for (TagAction action : actions) {
if (!syncID.equals(action.getSyncID())) {
continue;
}

List<P4PollRef> pollChanges = action.getPollPathChanges();
if (pollChanges == null || pollChanges.isEmpty()) {

Check warning on line 324 in src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 324 is only partially covered, 2 branches are missing
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)) {

Check warning on line 337 in src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java

View check run for this annotation

ci.jenkins.io / SpotBugs

HE_USE_OF_UNHASHABLE_CLASS

HIGH: org.jenkinsci.plugins.p4.changes.P4PollRef doesn't define a hashCode() method but is used in a hashed data structure in org.jenkinsci.plugins.p4.tagging.TagAction.getLastPollChange(Run, TaskListener, String)
Raw output
<p> A class defines an equals(Object) method but not a hashCode() method, and thus doesn't fulfill the requirement that equal objects have equal hashCodes. An instance of this class is used in a hash data structure, making the need to fix this problem of highest importance.
listener.getLogger().println("Found last poll change " + poll.toString() + " on syncID " + syncID + " path " + path);
}
}
}

Check warning on line 341 in src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 328-341 are not covered by tests

return new ArrayList<>(result);
}

/**
* Find the last action; use this for environment variable as the last action has the latest values.
*
Expand Down Expand Up @@ -353,4 +398,21 @@
public String getJenkinsPath() {
return jenkinsPath;
}

public void setPollPathChanges(List<P4PollRef> finalPollPathList) {
List<P4PollRef> changes = new ArrayList<>();

for(P4Ref ref : finalPollPathList) {

Check warning on line 405 in src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 405 is only partially covered, one branch is missing
if(ref != null) {
P4PollRef poll = new P4PollRef(ref.getChange(), ((P4PollRef)ref).getPollPath());
changes.add(poll);
}
}

Check warning on line 410 in src/main/java/org/jenkinsci/plugins/p4/tagging/TagAction.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 406-410 are not covered by tests
this.pollRefChanges = changes;
}

public List<P4PollRef> getPollPathChanges() {
return this.pollRefChanges;
}

}
60 changes: 60 additions & 0 deletions src/main/java/org/jenkinsci/plugins/p4/tasks/CheckoutTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@
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.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;
Expand All @@ -29,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<Boolean>, Serializable {

Expand Down Expand Up @@ -387,4 +392,59 @@
public void checkRoles(RoleChecker checker) throws SecurityException {
checker.check((RoleSensitive) this, Roles.SLAVE);
}


/**
* Resolve workspace-defined poll paths to their latest Perforce changes.
*
* <p>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<P4PollRef> resolvePollPathsToLatestChanges() {
final int MAX_PATHS_TO_CHECK = 10;

List<P4PollRef> results = new ArrayList<>();
Workspace ws = getWorkspace();

if (!(ws instanceof ManualWorkspaceImpl)) {
return results;
}

String pollSpec = ((ManualWorkspaceImpl) ws).getSpec().getPollPath();
if (pollSpec == null || pollSpec.trim().isEmpty()) {

Check warning on line 423 in src/main/java/org/jenkinsci/plugins/p4/tasks/CheckoutTask.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 423 is only partially covered, 3 branches are missing
return results;
}

List<String> 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;

Check warning on line 448 in src/main/java/org/jenkinsci/plugins/p4/tasks/CheckoutTask.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 427-448 are not covered by tests
}
}
Loading
Loading