From 526a5a0702d83196448b2e4fe524aba79570f36c Mon Sep 17 00:00:00 2001 From: Alejandro del Castillo Date: Thu, 4 Jan 2018 15:24:35 -0600 Subject: [PATCH 01/42] Dev/adelcast/fix author (#188) * TfsUserLookup.find: fix logging message Signed-off-by: Alejandro del Castillo * tfs: use owner instead of commiter as author It is not uncommon to have automatic processes to merge between branches, run validation before submission, etc. When that happens, the submitter may be different than the owner (the submitter tends to be an account used for automation). In those cases, the Jenkins UI will show the same name for all commits. Use owner name instead, as it better reflects the name of the person that authored the commit. Signed-off-by: Alejandro del Castillo --- .../plugins/tfs/commands/RemoteChangesetVersionCommand.java | 2 +- tfs/src/main/java/hudson/plugins/tfs/model/Project.java | 2 +- tfs/src/main/java/hudson/plugins/tfs/model/TfsUserLookup.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tfs/src/main/java/hudson/plugins/tfs/commands/RemoteChangesetVersionCommand.java b/tfs/src/main/java/hudson/plugins/tfs/commands/RemoteChangesetVersionCommand.java index 487479313..90a77387c 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/commands/RemoteChangesetVersionCommand.java +++ b/tfs/src/main/java/hudson/plugins/tfs/commands/RemoteChangesetVersionCommand.java @@ -83,7 +83,7 @@ public Integer call() throws Exception { final Changeset serverChangeset = serverChangeSets[0]; changeSetNumber = serverChangeset.getChangesetID(); final Date changeSetDate = serverChangeset.getDate().getTime(); - final String author = serverChangeset.getCommitter(); + final String author = serverChangeset.getOwner(); final SimpleDateFormat simpleDateFormat = DateUtil.TFS_DATETIME_FORMATTER.get(); simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); final String changeSetDateIso8601 = simpleDateFormat.format(changeSetDate); diff --git a/tfs/src/main/java/hudson/plugins/tfs/model/Project.java b/tfs/src/main/java/hudson/plugins/tfs/model/Project.java index 8e8bd0585..c4062bb45 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/model/Project.java +++ b/tfs/src/main/java/hudson/plugins/tfs/model/Project.java @@ -56,7 +56,7 @@ public String getProjectPath() { (com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Changeset serverChangeset, UserLookup userLookup) { final String version = Integer.toString(serverChangeset.getChangesetID(), 10); final Date date = serverChangeset.getDate().getTime(); - final String author = serverChangeset.getCommitter(); + final String author = serverChangeset.getOwner(); final User authorUser = userLookup.find(author); final String comment = serverChangeset.getComment(); diff --git a/tfs/src/main/java/hudson/plugins/tfs/model/TfsUserLookup.java b/tfs/src/main/java/hudson/plugins/tfs/model/TfsUserLookup.java index cc041a75b..d07176fd7 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/model/TfsUserLookup.java +++ b/tfs/src/main/java/hudson/plugins/tfs/model/TfsUserLookup.java @@ -34,11 +34,11 @@ public TfsUserLookup(final IIdentityManagementService ims, final UserAccountMapp */ public User find(final String accountName) { final String mappedAccountName = userAccountMapper.mapUserAccount(accountName); - logger.log(Level.FINE, "Looking up Jenkins user for account '%s'.", mappedAccountName); + logger.log(Level.FINE, String.format("Looking up Jenkins user for account '%s'.", mappedAccountName)); final User jenkinsUser = User.get(mappedAccountName); Mailer.UserProperty mailerProperty = jenkinsUser.getProperty(Mailer.UserProperty.class); if (mailerProperty == null || mailerProperty.getAddress() == null || mailerProperty.getAddress().length() == 0) { - logger.log(Level.FINE, "No Mailer.UserProperty defined for '%s', looking in TFS", mappedAccountName); + logger.log(Level.FINE, String.format("No Mailer.UserProperty defined for '%s', looking in TFS", mappedAccountName)); final TeamFoundationIdentity tfsUser = ims.readIdentity( IdentitySearchFactor.ACCOUNT_NAME, accountName, From cdda7a0ac6f08fbbd2b3572acc03522ccd507813 Mon Sep 17 00:00:00 2001 From: Chenyang Liu Date: Thu, 8 Feb 2018 04:52:43 +0800 Subject: [PATCH 02/42] Release configuration and archiving VSTS logs improvement (#189) * Change configuration ways in Trigger release in TFS/Team Service * Finish archive logs from VSTS * Update help file for ReleaseManagementCI * Remove wildcard * Add timeout check and allow empty username * Remove archive logs and add a log link * Rename log to release * Delete releaseDetails * Add logs * Change back new line * Add logs --- .../tfs/TeamCollectionConfiguration.java | 59 +++++- .../plugins/tfs/rm/ReleaseManagementCI.java | 186 +++++++++++++++++- .../tfs/rm/ReleaseManagementHttpClient.java | 35 ++-- .../plugins/tfs/rm/ReleaseSummaryAction.java | 37 ++++ .../tfs/rm/ReleaseManagementCI/config.jelly | 15 +- .../help-credentialsId.html | 3 + .../help-destinationPath.html | 3 + .../help-isArchiveLog.html | 3 + .../rm/ReleaseManagementCI/help-password.html | 3 - .../rm/ReleaseManagementCI/help-username.html | 3 - .../tfs/rm/ReleaseSummaryAction/summary.jelly | 6 + 11 files changed, 308 insertions(+), 45 deletions(-) create mode 100644 tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseSummaryAction.java create mode 100644 tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-credentialsId.html create mode 100644 tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-destinationPath.html create mode 100644 tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-isArchiveLog.html delete mode 100644 tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-password.html delete mode 100644 tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-username.html create mode 100644 tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseSummaryAction/summary.jelly diff --git a/tfs/src/main/java/hudson/plugins/tfs/TeamCollectionConfiguration.java b/tfs/src/main/java/hudson/plugins/tfs/TeamCollectionConfiguration.java index edff52f3e..8e0e5e67c 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/TeamCollectionConfiguration.java +++ b/tfs/src/main/java/hudson/plugins/tfs/TeamCollectionConfiguration.java @@ -1,17 +1,26 @@ //CHECKSTYLE:OFF package hudson.plugins.tfs; +import com.cloudbees.plugins.credentials.Credentials; import com.cloudbees.plugins.credentials.CredentialsMatcher; import com.cloudbees.plugins.credentials.CredentialsMatchers; import com.cloudbees.plugins.credentials.CredentialsProvider; +import com.cloudbees.plugins.credentials.CredentialsScope; +import com.cloudbees.plugins.credentials.SystemCredentialsProvider; import com.cloudbees.plugins.credentials.common.StandardListBoxModel; import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; +import com.cloudbees.plugins.credentials.domains.Domain; import com.cloudbees.plugins.credentials.domains.DomainRequirement; +import com.cloudbees.plugins.credentials.domains.DomainSpecification; import com.cloudbees.plugins.credentials.domains.HostnameRequirement; +import com.cloudbees.plugins.credentials.domains.HostnameSpecification; +import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl; import com.microsoft.tfs.core.exceptions.TFSUnauthorizedException; import hudson.Extension; import hudson.model.AbstractDescribableImpl; import hudson.model.Descriptor; +import hudson.model.Item; +import hudson.model.ItemGroup; import hudson.plugins.tfs.model.ConnectionParameters; import hudson.plugins.tfs.model.ListOfGitRepositories; import hudson.plugins.tfs.model.MockableVersionControlClient; @@ -33,6 +42,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.UUID; +import java.util.logging.Level; import java.util.logging.Logger; public class TeamCollectionConfiguration extends AbstractDescribableImpl { @@ -219,20 +230,36 @@ static StandardUsernamePasswordCredentials findCredential(final String hostName, return result; } - static List findCredentials(final String hostName) { + public static List findCredentials(final String hostName) { final Jenkins jenkins = Jenkins.getInstance(); + return findCredentials(hostName, jenkins); + } + + public static List findCredentials(final String hostName, ItemGroup own) { final HostnameRequirement requirement = new HostnameRequirement(hostName); final List matches = CredentialsProvider.lookupCredentials( StandardUsernamePasswordCredentials.class, - jenkins, + own, + ACL.SYSTEM, + requirement + ); + return matches; + } + + public static List findCredentials(final String hostName, Item own) { + final HostnameRequirement requirement = new HostnameRequirement(hostName); + final List matches = + CredentialsProvider.lookupCredentials( + StandardUsernamePasswordCredentials.class, + own, ACL.SYSTEM, requirement ); return matches; } - static StandardUsernamePasswordCredentials findCredentialsById(final String credentialsId) { + public static StandardUsernamePasswordCredentials findCredentialsById(final String credentialsId) { final Jenkins jenkins = Jenkins.getInstance(); final List matches = CredentialsProvider.lookupCredentials( @@ -246,6 +273,32 @@ static StandardUsernamePasswordCredentials findCredentialsById(final String cred return result; } + public static String setCredentials(final String hostName, String username, String password) { + List domainSpecifications = new ArrayList<>(); + domainSpecifications.add(new HostnameSpecification(hostName, null)); + Domain domain = new Domain("Generated for " + hostName, "", domainSpecifications); + + SystemCredentialsProvider.getInstance().getDomainCredentialsMap().put(domain, new ArrayList()); + + String credentialsId; + StandardUsernamePasswordCredentials newCredential = new UsernamePasswordCredentialsImpl( + CredentialsScope.GLOBAL, + credentialsId = UUID.randomUUID().toString(), + "Generated for " + username, + username, + password + ); + SystemCredentialsProvider.getInstance().getDomainCredentialsMap().get(domain).add(newCredential); + + try { + SystemCredentialsProvider.getInstance().save(); + } catch (IOException ex) { + LOGGER.log(Level.WARNING, "SystemCredentialsProvider instance save failed: ", ex); + } + + return credentialsId; + } + // TODO: we'll probably also want findCredentialsForGitRepo, where we match part of the URL path public static StandardUsernamePasswordCredentials findCredentialsForCollection(final URI collectionUri) { final TeamPluginGlobalConfig config = TeamPluginGlobalConfig.get(); diff --git a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseManagementCI.java b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseManagementCI.java index 73123902d..0c865b60f 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseManagementCI.java +++ b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseManagementCI.java @@ -1,38 +1,57 @@ //CHECKSTYLE:OFF package hudson.plugins.tfs.rm; +import com.cloudbees.plugins.credentials.common.StandardListBoxModel; +import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; import com.google.gson.Gson; import hudson.Launcher; import hudson.Extension; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; +import hudson.model.Item; import hudson.model.Result; +import hudson.plugins.tfs.TeamCollectionConfiguration; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.BuildStepMonitor; import hudson.tasks.Notifier; import hudson.tasks.Publisher; +import hudson.util.ListBoxModel; import hudson.util.Secret; +import org.apache.commons.lang.StringUtils; +import org.json.JSONException; +import org.json.JSONObject; +import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import java.io.IOException; +import java.io.Serializable; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.json.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.kohsuke.stapler.QueryParameter; /** * @author Ankit Goyal */ -public class ReleaseManagementCI extends Notifier{ +public class ReleaseManagementCI extends Notifier implements Serializable { + + private static final long serialVersionUID = -760016860995557L; + private static final Logger logger = Logger.getLogger(ReleaseManagementCI.class.getName()); + public final String collectionUrl; public final String projectName; public final String releaseDefinitionName; - public final String username; - public final Secret password; - - // Fields in config.jelly must match the parameter names in the "DataBoundConstructor" - @DataBoundConstructor + public transient String username; + public transient Secret password; + public String credentialsId; + + public ReleaseManagementCI(String collectionUrl, String projectName, String releaseDefinitionName, String username, Secret password) { if (collectionUrl.endsWith("/")) @@ -51,6 +70,55 @@ public ReleaseManagementCI(String collectionUrl, String projectName, String rele this.password = password; } + // Fields in config.jelly must match the parameter names in the "DataBoundConstructor" + @DataBoundConstructor + public ReleaseManagementCI(String collectionUrl, + String projectName, + String releaseDefinitionName, + String credentialsId) { + if (collectionUrl.endsWith("/")) + { + this.collectionUrl = collectionUrl; + } + else + { + this.collectionUrl = collectionUrl + "/"; + } + + this.projectName = projectName; + this.releaseDefinitionName = releaseDefinitionName; + StandardUsernamePasswordCredentials credential = TeamCollectionConfiguration.findCredentialsById(credentialsId); + this.username = credential == null ? "" : credential.getUsername(); + this.password = credential == null ? Secret.fromString("") : credential.getPassword(); + this.credentialsId = credentialsId; + } + + protected Object readResolve() { + if (StringUtils.isNotBlank(collectionUrl) + && password != null + && StringUtils.isNotBlank(password.getPlainText())) { + try { + final URI uri = new URI(collectionUrl); + String hostName = uri.getHost(); + List credentials = TeamCollectionConfiguration.findCredentials(hostName); + for (StandardUsernamePasswordCredentials credential : credentials) { + if ((StringUtils.isBlank(username) || credential.getUsername().equals(username)) + && credential.getPassword().getPlainText().equals(password.getPlainText())) { + this.credentialsId = credential.getId(); + return this; + } + } + this.credentialsId + = TeamCollectionConfiguration.setCredentials(hostName, username, password.getPlainText()); + } catch (Exception ex) { + logger.log(Level.WARNING, + String.format("Get or generate credentials for collection url: %s and username: %s failed.", collectionUrl, username), + ex); + } + } + return this; + } + /* * (non-Javadoc) * @@ -71,6 +139,10 @@ public BuildStepMonitor getRequiredMonitorService() { @Override public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + StandardUsernamePasswordCredentials credential = TeamCollectionConfiguration.findCredentialsById(credentialsId); + this.username = credential == null ? "" : credential.getUsername(); + this.password = credential == null ? Secret.fromString("") : credential.getPassword(); + String jobName = build.getProject().getName(); int buildId = build.number; String buildNumber = build.getDisplayName(); @@ -102,7 +174,7 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListen } else { - CreateRelease(releaseManagementHttpClient, releaseDefinition, jobName, buildNumber, buildId, listener); + CreateRelease(releaseManagementHttpClient, releaseDefinition, jobName, buildNumber, buildId, listener, build, launcher); } } catch (ReleaseManagementException ex) @@ -124,7 +196,9 @@ void CreateRelease( String jobName, String buildNumber, int buildId, - BuildListener listener) throws ReleaseManagementException, JSONException + BuildListener listener, + AbstractBuild build, + Launcher launcher) throws ReleaseManagementException, JSONException { Artifact jenkinsArtifact = null; for(final Artifact artifact : releaseDefinition.getArtifacts()) @@ -163,6 +237,8 @@ void CreateRelease( JSONObject object = new JSONObject(response); listener.getLogger().printf("Release Name: %s%n", object.getString("name")); listener.getLogger().printf("Release id: %s%n", object.getString("id")); + build.addAction(new ReleaseSummaryAction(jobName, buildId, + object.getJSONObject("_links").getJSONObject("web").getString("href"))); } } @@ -227,5 +303,97 @@ public String getDisplayName() return "Trigger release in TFS/Team Services"; } + public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Item owner, + @QueryParameter String collectionUrl) { + StandardListBoxModel listBoxModel = new StandardListBoxModel(); + listBoxModel.withEmptySelection(); + String hostName = null; + try { + final URI uri = new URI(collectionUrl); + hostName = uri.getHost(); + } + catch (final URISyntaxException ignored) { + } + listBoxModel.withAll(TeamCollectionConfiguration.findCredentials(hostName, owner)); + return listBoxModel; + } + + public ListBoxModel doFillProjectNameItems(@QueryParameter String collectionUrl, + @QueryParameter String credentialsId) { + StandardListBoxModel listBoxModel = new StandardListBoxModel(); + listBoxModel.withEmptySelection(); + + if (StringUtils.isBlank(collectionUrl) || StringUtils.isBlank(credentialsId)) { + return listBoxModel; + } + + StandardUsernamePasswordCredentials credential + = TeamCollectionConfiguration.findCredentialsById(credentialsId); + if (credential == null) { + return listBoxModel; + } + + String username = credential.getUsername(); + Secret password = credential.getPassword(); + + try { + ReleaseManagementHttpClient releaseManagementHttpClient = + new ReleaseManagementHttpClient( + collectionUrl.toLowerCase(), + username, + password); + + List projects = releaseManagementHttpClient.GetProjectItems(); + for (Project project : projects) { + listBoxModel.add(project.getName()); + } + } catch (ReleaseManagementException ex) { + logger.log(Level.WARNING, + String.format("Get team project for collection url: %s failed.", collectionUrl), + ex); + } + return listBoxModel; + } + + public ListBoxModel doFillReleaseDefinitionNameItems(@QueryParameter String collectionUrl, + @QueryParameter String credentialsId, + @QueryParameter String projectName) { + StandardListBoxModel listBoxModel = new StandardListBoxModel(); + listBoxModel.withEmptySelection(); + + if (StringUtils.isBlank(collectionUrl) + || StringUtils.isBlank(credentialsId) + || StringUtils.isBlank(projectName)) { + return listBoxModel; + } + + StandardUsernamePasswordCredentials credential + = TeamCollectionConfiguration.findCredentialsById(credentialsId); + if (credential == null) { + return listBoxModel; + } + String username = credential.getUsername(); + Secret password = credential.getPassword(); + + try { + ReleaseManagementHttpClient releaseManagementHttpClient = + new ReleaseManagementHttpClient( + collectionUrl.toLowerCase().replaceFirst(".visualstudio.com", ".vsrm.visualstudio.com"), + username, + password); + + List releaseDefinitions = releaseManagementHttpClient.GetReleaseDefinitions(projectName); + for (ReleaseDefinition releaseDefinition : releaseDefinitions) { + listBoxModel.add(releaseDefinition.getName()); + } + } catch (ReleaseManagementException ex) { + logger.log(Level.WARNING, + String.format("Get release definition for project: %s failed.", projectName), + ex); + } + + return listBoxModel; + } + } } diff --git a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseManagementHttpClient.java b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseManagementHttpClient.java index fa1c03128..f8daad158 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseManagementHttpClient.java +++ b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseManagementHttpClient.java @@ -3,17 +3,20 @@ import com.google.gson.Gson; import hudson.util.Secret; -import java.io.IOException; + import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; + import org.apache.commons.codec.binary.Base64; import org.apache.commons.httpclient.HttpClient; -import org.apache.commons.httpclient.HttpException; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; +import org.json.JSONException; +import org.json.JSONObject; /** * @author Ankit Goyal @@ -57,6 +60,18 @@ public ReleaseArtifactVersionsResponse GetVersions(String project, List GetProjectItems() throws ReleaseManagementException + { + String url = this.accountUrl + "/_apis/projects?api-version=1.0"; + String response = this.ExecuteGetMethod(url); + try { + String values = new JSONObject(response).getString("value"); + return Arrays.asList(new Gson().fromJson(values, Project[].class)); + } catch (JSONException ex) { + throw new ReleaseManagementException(ex); + } + } private String ExecutePostmethod(String url, String body) throws ReleaseManagementException { @@ -74,14 +89,6 @@ private String ExecutePostmethod(String url, String body) throws ReleaseManageme throw new ReleaseManagementException("Error occurred.%nStatus: " + status + "%nResponse: " + response + "%n"); } } - catch(HttpException ex) - { - throw new ReleaseManagementException(ex); - } - catch(IOException ex) - { - throw new ReleaseManagementException(ex); - } catch(Exception ex) { throw new ReleaseManagementException(ex); @@ -104,14 +111,6 @@ private String ExecuteGetMethod(String url) throws ReleaseManagementException throw new ReleaseManagementException("Error occurred.%nStatus: " + status + "%nResponse: " + response + "%n"); } } - catch(HttpException ex) - { - throw new ReleaseManagementException(ex); - } - catch(IOException ex) - { - throw new ReleaseManagementException(ex); - } catch(Exception ex) { throw new ReleaseManagementException(ex); diff --git a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseSummaryAction.java b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseSummaryAction.java new file mode 100644 index 000000000..e991c1b28 --- /dev/null +++ b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseSummaryAction.java @@ -0,0 +1,37 @@ +package hudson.plugins.tfs.rm; + +import hudson.model.InvisibleAction; +import org.kohsuke.stapler.export.ExportedBean; + +/** + * Show release summary link in build page. + */ + +@ExportedBean +public class ReleaseSummaryAction extends InvisibleAction { + private String projectName; + private int buildNo; + private String releaseLink; + + public ReleaseSummaryAction() { + + } + + public ReleaseSummaryAction(final String projectName, final int buildNo, final String releaseLink) { + this.projectName = projectName; + this.buildNo = buildNo; + this.releaseLink = releaseLink; + } + + public String getProjectName() { + return projectName; + } + + public int getBuildNo() { + return buildNo; + } + + public String getReleaseLink() { + return releaseLink; + } +} diff --git a/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/config.jelly b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/config.jelly index 2186777ec..bfdb3fb5b 100644 --- a/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/config.jelly +++ b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/config.jelly @@ -1,18 +1,15 @@ - + + + + - + - - - - - - - + diff --git a/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-credentialsId.html b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-credentialsId.html new file mode 100644 index 000000000..796a1eb3a --- /dev/null +++ b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-credentialsId.html @@ -0,0 +1,3 @@ +
+Select the credential for you collection. +
diff --git a/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-destinationPath.html b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-destinationPath.html new file mode 100644 index 000000000..f042eb6aa --- /dev/null +++ b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-destinationPath.html @@ -0,0 +1,3 @@ +
+Specify the destination path to save logs from VSTS/TFS. The base directory is the workspace. Leave it blank if you don't need to get logs from VSTS/TFS. +
diff --git a/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-isArchiveLog.html b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-isArchiveLog.html new file mode 100644 index 000000000..869291043 --- /dev/null +++ b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-isArchiveLog.html @@ -0,0 +1,3 @@ +
+Check if you need to add log file to the artifact. +
diff --git a/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-password.html b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-password.html deleted file mode 100644 index c237de42d..000000000 --- a/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-password.html +++ /dev/null @@ -1,3 +0,0 @@ -
-Enter password (credentials) if you are using TFS. Provide PAT to connect to VS Team Services. -
diff --git a/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-username.html b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-username.html deleted file mode 100644 index 38b3a507b..000000000 --- a/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseManagementCI/help-username.html +++ /dev/null @@ -1,3 +0,0 @@ -
-Provide username (credentials) if you are using TFS. Leave blank if you are using VS Team Services. -
diff --git a/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseSummaryAction/summary.jelly b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseSummaryAction/summary.jelly new file mode 100644 index 000000000..f63651011 --- /dev/null +++ b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseSummaryAction/summary.jelly @@ -0,0 +1,6 @@ + + + + Release summary: ${it.releaseLink} + + From f6fe6de50a52e4c0fe3419b996bdc8204c912210 Mon Sep 17 00:00:00 2001 From: Petrik van der Velde Date: Thu, 8 Feb 2018 10:00:16 +1300 Subject: [PATCH 03/42] Allow users to map sub-folders of cloaked paths (#181) * Adding the UI for mapped paths * Pushing the mapped paths to the WorkspaceConfiguration * Updating the functions for getting history from TFS paths * The local part of the mapped path may be unspecified. * Checking out with mapped paths * Fixing issues due to rebase * Adding a test case to verify that deserialization with a TFS path only works. * The mapped path may not be specified in some cases in which case we just translate the TFS path. Upon deserialization we should make sure not to try and read the mapped path if it doesn't exist --- .../plugins/tfs/TeamFoundationServerScm.java | 129 ++++++++++++- .../plugins/tfs/actions/CheckoutAction.java | 9 +- .../tfs/commands/NewWorkspaceCommand.java | 32 +++- .../hudson/plugins/tfs/model/Project.java | 40 ++-- .../tfs/model/WorkspaceConfiguration.java | 42 ++++- .../hudson/plugins/tfs/model/Workspaces.java | 4 +- .../tfs/TeamFoundationServerScm/config.jelly | 4 + .../help-mappedPaths.html | 56 ++++++ .../tfs/TeamFoundationServerScmTest.java | 177 ++++++++++++++++-- .../tfs/actions/CheckoutActionTest.java | 108 ++++++----- .../tfs/commands/NewWorkspaceCommandTest.java | 121 +++++++++++- .../hudson/plugins/tfs/model/ProjectTest.java | 133 +++++++++++-- .../tfs/model/WorkspaceConfigurationTest.java | 46 ++++- .../plugins/tfs/model/WorkspacesTest.java | 11 +- ...ldWorkspaceConfigurationRetrieverTest.java | 9 +- 15 files changed, 801 insertions(+), 120 deletions(-) create mode 100644 tfs/src/main/resources/hudson/plugins/tfs/TeamFoundationServerScm/help-mappedPaths.html diff --git a/tfs/src/main/java/hudson/plugins/tfs/TeamFoundationServerScm.java b/tfs/src/main/java/hudson/plugins/tfs/TeamFoundationServerScm.java index 86f684668..82adc55b1 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/TeamFoundationServerScm.java +++ b/tfs/src/main/java/hudson/plugins/tfs/TeamFoundationServerScm.java @@ -1,3 +1,4 @@ +//CHECKSTYLE:OFF package hudson.plugins.tfs; import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; @@ -57,8 +58,12 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.TreeMap; +import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; @@ -85,6 +90,7 @@ public class TeamFoundationServerScm extends SCM { private final String serverUrl; private final String projectPath; private Collection cloakedPaths; + private Map mappedPaths; private String localPath; private final String workspaceName; @Deprecated private String userPassword; @@ -230,7 +236,16 @@ public String getCloakedPaths() { @DataBoundSetter public void setCloakedPaths(final String cloakedPaths) { - this.cloakedPaths = splitCloakedPaths(cloakedPaths); + this.cloakedPaths = deserializeCloakedPathCollectionFromString(cloakedPaths); + } + + public String getMappedPaths() { + return serializeMappedPathCollectionToString(this.mappedPaths); + } + + @DataBoundSetter + public void setMappedPaths(final String mappedPaths) { + this.mappedPaths = deserializeMappedPathCollectionFromString(mappedPaths); } // Bean properties END @@ -239,6 +254,32 @@ static String serializeCloakedPathCollectionToString(final Collection cl return cloakedPaths == null ? StringUtils.EMPTY : StringUtils.join(cloakedPaths, "\n"); } + static String serializeMappedPathCollectionToString(final Map mappedPaths) { + if (mappedPaths == null) { + return StringUtils.EMPTY; + } + + Map sortedPaths = new TreeMap(mappedPaths); + Iterator> iter = sortedPaths.entrySet().iterator(); + + StringBuilder sb = new StringBuilder(); + while (iter.hasNext()) { + Entry entry = iter.next(); + sb.append(entry.getKey()); + + final String mappedPath = entry.getValue(); + if (mappedPath != null) { + sb.append(' ').append(':').append(' '); + sb.append(entry.getValue()); + } + if (iter.hasNext()) { + sb.append('\n'); + } + } + + return sb.toString(); + } + String getWorkspaceName(final Run build, final Computer computer) { normalizedWorkspaceName = workspaceName; if (build != null) { @@ -274,6 +315,29 @@ Collection getCloakedPaths(final Run run) { return paths; } + Map getMappedPaths(final Run run) { + final Map paths = new TreeMap(); + if (mappedPaths != null) { + final BuildVariableResolver resolver = new BuildVariableResolver(run.getParent()); + + Iterator> iter = mappedPaths.entrySet().iterator(); + while (iter.hasNext()) { + Entry entry = iter.next(); + final String serverPath = Util.replaceMacro(substituteBuildParameter(run, entry.getKey()), resolver); + + String localPath = null; + String storedLocalPath = entry.getValue(); + if (storedLocalPath != null) { + localPath = Util.replaceMacro(substituteBuildParameter(run, storedLocalPath), resolver); + } + + paths.put(serverPath, localPath); + } + } + + return paths; + } + private String substituteBuildParameter(final Run run, final String text) { if (run instanceof AbstractBuild) { AbstractBuild build = (AbstractBuild) run; @@ -284,7 +348,7 @@ private String substituteBuildParameter(final Run run, final String text) return text; } - static Collection splitCloakedPaths(final String cloakedPaths) { + static Collection deserializeCloakedPathCollectionFromString(final String cloakedPaths) { final List cloakedPathsList = new ArrayList(); if (cloakedPaths != null && cloakedPaths.length() > 0) { final StringBuilder cloakedPath = new StringBuilder(cloakedPaths.length()); @@ -308,11 +372,53 @@ static Collection splitCloakedPaths(final String cloakedPaths) { return cloakedPathsList; } + static Map deserializeMappedPathCollectionFromString(final String mappedPaths){ + final Map mappedPathsCollection = new HashMap(); + if (mappedPaths != null && mappedPaths.length() > 0) { + final StringBuilder path = new StringBuilder(mappedPaths.length()); + for (final char character : mappedPaths.toCharArray()) { + switch (character) { + case '\n': + if (path.length() > 0) { + String[] combinedPaths = splitMappedPath(path.toString()); + String localPath = null; + if (combinedPaths.length > 1) { + localPath = combinedPaths[1].trim(); + } + mappedPathsCollection.put(combinedPaths[0].trim(), localPath); + + path.setLength(0); + } + break; + default: + path.append(character); + break; + } + } + + if (path.length() > 0) { + String[] combinedPaths = splitMappedPath(path.toString()); + + final String serverPath = combinedPaths[0].trim(); + String localPath = null; + if (combinedPaths.length > 1) { + localPath = combinedPaths[1].trim(); + } + mappedPathsCollection.put(serverPath, localPath); + } + } + return mappedPathsCollection; + } + + static String[] splitMappedPath(final String mappedPath) { + return mappedPath.split(":", 2); + } + @Override public void checkout(final Run build, final Launcher launcher, final FilePath workspaceFilePath, final TaskListener listener, final File changelogFile, final SCMRevisionState baseline) throws IOException, InterruptedException { Server server = createServer(launcher, listener, build); try { - WorkspaceConfiguration workspaceConfiguration = new WorkspaceConfiguration(server.getUrl(), getWorkspaceName(build, workspaceFilePath.toComputer()), getProjectPath(build), getCloakedPaths(build), getLocalPath()); + WorkspaceConfiguration workspaceConfiguration = new WorkspaceConfiguration(server.getUrl(), getWorkspaceName(build, workspaceFilePath.toComputer()), getProjectPath(build), getCloakedPaths(build), getMappedPaths(build), getLocalPath()); final Run previousBuild = build.getPreviousBuild(); // Check if the configuration has changed if (previousBuild != null) { @@ -342,7 +448,7 @@ public void checkout(final Run build, final Launcher launcher, final FileP final Project project = server.getProject(projPath); final int changeSet = recordWorkspaceChangesetVersion(build, listener, project, projPath, singleVersionSpec); - CheckoutAction action = new CheckoutAction(workspaceConfiguration.getWorkspaceName(), workspaceConfiguration.getProjectPath(), workspaceConfiguration.getCloakedPaths(), workspaceConfiguration.getWorkfolder(), isUseUpdate(), isUseOverwrite()); + CheckoutAction action = new CheckoutAction(workspaceConfiguration.getWorkspaceName(), workspaceConfiguration.getProjectPath(), workspaceConfiguration.getCloakedPaths(), workspaceConfiguration.getMappedPaths(), workspaceConfiguration.getWorkfolder(), isUseUpdate(), isUseOverwrite()); List list; if (StringUtils.isNotEmpty(singleVersionSpec)) { list = action.checkoutBySingleVersionSpec(server, workspaceFilePath, singleVersionSpec); @@ -415,7 +521,8 @@ public boolean pollChanges(final AbstractProject hudsonProject, final Launcher l return (server.getProject(getProjectPath(lastRun)).getDetailedHistoryWithoutCloakedPaths( lastRun.getTimestamp(), Calendar.getInstance(), - getCloakedPaths(lastRun) + getCloakedPaths(lastRun), + getMappedPaths(lastRun).keySet() ).size() > 0); } finally { server.close(); @@ -580,6 +687,7 @@ public static class DescriptorImpl extends SCMDescriptor\\|\\*\\?]+[^\\s\\.\"/:<>\\|\\*\\?]$", Pattern.CASE_INSENSITIVE); public static final Pattern PROJECT_PATH_REGEX = Pattern.compile("^\\$\\/.*", Pattern.CASE_INSENSITIVE); public static final Pattern CLOAKED_PATHS_REGEX = Pattern.compile("\\s*\\$[^\\n;]+(\\s*[\\n]\\s*\\$[^\\n;]+){0,}\\s*", Pattern.CASE_INSENSITIVE); + public static final Pattern MAPPED_PATHS_REGEX = Pattern.compile("\\s*\\$[^\\n;]+(\\s*[\\n]\\s*\\$[^\\n;]+){0,}\\s*", Pattern.CASE_INSENSITIVE); @Override public boolean isApplicable(final Job project) { @@ -654,6 +762,12 @@ public FormValidation doWorkspaceNameCheck(@QueryParameter final String value) { public FormValidation doCloakedPathsCheck(@QueryParameter final String value) { return doRegexCheck(new Pattern[]{CLOAKED_PATHS_REGEX}, "Each cloaked path must begin with '$/'. Multiple paths must be separated by blank lines.", + null, value ); + } + + public FormValidation doMappedPathsCheck(@QueryParameter final String value) { + return doRegexCheck(new Pattern[]{MAPPED_PATHS_REGEX}, + "Each mapped path must begin with '$/'. Multiple paths must be separated by blank lines.", null, value); } @@ -714,7 +828,8 @@ public PollingResult compareRemoteRevisionWith( return (server.getProject(getProjectPath(build)).getDetailedHistoryWithoutCloakedPaths( build.getTimestamp(), Calendar.getInstance(), - getCloakedPaths(build) + getCloakedPaths(build), + getMappedPaths(build).keySet() ).size() > 0) ? PollingResult.BUILD_NOW : PollingResult.NO_CHANGES; } finally { server.close(); @@ -729,7 +844,7 @@ public PollingResult compareRemoteRevisionWith( } final Project tfsProject = server.getProject(projectPath); try { - final ChangeSet latest = tfsProject.getLatestUncloakedChangeset(tfsBaseline.changesetVersion, cloakedPaths); + final ChangeSet latest = tfsProject.getLatestUncloakedChangeset(tfsBaseline.changesetVersion, cloakedPaths, mappedPaths.keySet()); final TFSRevisionState tfsRemote = (latest != null) ? new TFSRevisionState(latest.getVersion(), projectPath) diff --git a/tfs/src/main/java/hudson/plugins/tfs/actions/CheckoutAction.java b/tfs/src/main/java/hudson/plugins/tfs/actions/CheckoutAction.java index a82d323b8..f45acd883 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/actions/CheckoutAction.java +++ b/tfs/src/main/java/hudson/plugins/tfs/actions/CheckoutAction.java @@ -20,20 +20,23 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Map; public class CheckoutAction { private final String workspaceName; private final String projectPath; private final Collection cloakedPaths; + private final Map mappedPaths; private final String localFolder; private final boolean useUpdate; private final boolean useOverwrite; - public CheckoutAction(String workspaceName, String projectPath, Collection cloakedPaths, String localFolder, boolean useUpdate, boolean useOverwrite) { + public CheckoutAction(String workspaceName, String projectPath, Collection cloakedPaths, Map mappedPaths, String localFolder, boolean useUpdate, boolean useOverwrite) { this.workspaceName = workspaceName; this.projectPath = projectPath; this.cloakedPaths = cloakedPaths; + this.mappedPaths = mappedPaths; this.localFolder = localFolder; this.useUpdate = useUpdate; this.useOverwrite = useOverwrite; @@ -63,7 +66,7 @@ public List checkout(final Server server, final FilePath workspacePat project.getFiles(normalizedFolder, versionSpecString, useOverwrite); if (lastBuildVersionSpec != null) { - return project.getDetailedHistoryWithoutCloakedPaths(lastBuildVersionSpec, currentBuildVersionSpec, cloakedPaths); + return project.getDetailedHistoryWithoutCloakedPaths(lastBuildVersionSpec, currentBuildVersionSpec, cloakedPaths, mappedPaths.keySet()); } return new ArrayList(); @@ -141,7 +144,7 @@ else if (existingWorkspaceName.equalsIgnoreCase(workspaceName)) { localFolderPath.deleteContents(); } final String serverPath = project.getProjectPath(); - workspace = workspaces.newWorkspace(workspaceName, serverPath, cloakedPaths, localPath); + workspace = workspaces.newWorkspace(workspaceName, serverPath, cloakedPaths, mappedPaths, localPath); } else { workspace = workspaces.getWorkspace(workspaceName); } diff --git a/tfs/src/main/java/hudson/plugins/tfs/commands/NewWorkspaceCommand.java b/tfs/src/main/java/hudson/plugins/tfs/commands/NewWorkspaceCommand.java index 8eeab2f9c..b605c3dc9 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/commands/NewWorkspaceCommand.java +++ b/tfs/src/main/java/hudson/plugins/tfs/commands/NewWorkspaceCommand.java @@ -14,11 +14,17 @@ import hudson.plugins.tfs.model.Server; import hudson.remoting.Callable; +import java.io.File; import java.io.IOException; import java.io.PrintStream; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; public class NewWorkspaceCommand extends AbstractCallableCommand { @@ -32,13 +38,15 @@ public class NewWorkspaceCommand extends AbstractCallableCommand cloakedPaths; + private final Map mappedPaths; private final String localPath; - public NewWorkspaceCommand(final ServerConfigurationProvider server, final String workspaceName, final String serverPath, Collection cloakedPaths, final String localPath) { + public NewWorkspaceCommand(final ServerConfigurationProvider server, final String workspaceName, final String serverPath, Collection cloakedPaths, Map mappedPaths, final String localPath) { super(server); this.workspaceName = workspaceName; this.serverPath = serverPath; this.cloakedPaths = cloakedPaths; + this.mappedPaths = mappedPaths; this.localPath = localPath; } @@ -59,7 +67,7 @@ public Void call() throws IOException { WorkingFolder[] foldersToMap = null; if (serverPath != null && localPath != null) { - final String mappingMessage = String.format(MappingTemplate, serverPath, localPath, workspaceName); + String mappingMessage = String.format(MappingTemplate, serverPath, localPath, workspaceName); logger.println(mappingMessage); final List folderList = new ArrayList(); @@ -73,6 +81,26 @@ public Void call() throws IOException { folderList.add(new WorkingFolder(cloakedPath, null, WorkingFolderType.CLOAK)); } + + Iterator> iter = mappedPaths.entrySet().iterator(); + while (iter.hasNext()) { + Entry entry = iter.next(); + final String mappedServerPath = entry.getKey(); + final String storedMappedLocalPath = entry.getValue(); + String mappedLocalPath = null; + if (storedMappedLocalPath != null) { + mappedLocalPath = Paths.get(localPath, storedMappedLocalPath).toString().replaceAll(Matcher.quoteReplacement(File.separator), "/"); + } + else { + final String relativePath = mappedServerPath.substring(serverPath.length()); + mappedLocalPath = Paths.get(localPath, relativePath).toString().replaceAll(Matcher.quoteReplacement(File.separator), "/"); + } + + mappingMessage = String.format(MappingTemplate, mappedServerPath, mappedLocalPath, workspaceName); + logger.println(mappingMessage); + + folderList.add(new WorkingFolder(mappedServerPath, LocalPath.canonicalize(mappedLocalPath), WorkingFolderType.MAP, RecursionType.FULL)); + } foldersToMap = folderList.toArray(EMPTY_WORKING_FOLDER_ARRAY); } diff --git a/tfs/src/main/java/hudson/plugins/tfs/model/Project.java b/tfs/src/main/java/hudson/plugins/tfs/model/Project.java index c4062bb45..3a7a4662c 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/model/Project.java +++ b/tfs/src/main/java/hudson/plugins/tfs/model/Project.java @@ -175,16 +175,17 @@ public ChangeSet getLatestChangeset() { * Gets the latest changeset that isn't in a cloaked path. * @param fromChangeset the changeset that was last seen, as a point of reference * @param cloakedPaths the list of cloaked paths in the project + * @param mappedPaths the list of server paths that are explicitly mapped in the project * @return the {@link ChangeSet} instance representing the last entry in the history for the path */ - public ChangeSet getLatestUncloakedChangeset(final int fromChangeset, final Collection cloakedPaths) { + public ChangeSet getLatestUncloakedChangeset(final int fromChangeset, final Collection cloakedPaths, final Collection mappedPaths) { final ChangesetVersionSpec fromVersion = new ChangesetVersionSpec(fromChangeset); final List changeSets = getVCCHistory(fromVersion, LatestVersionSpec.INSTANCE, true, Integer.MAX_VALUE); - final ChangeSet result = findLatestUncloakedChangeset(cloakedPaths, changeSets); + final ChangeSet result = findLatestUncloakedChangeset(cloakedPaths, mappedPaths, changeSets); return result; } - static ChangeSet findLatestUncloakedChangeset(final Collection cloakedPaths, final List changeSets) { + static ChangeSet findLatestUncloakedChangeset(final Collection cloakedPaths, final Collection mappedPaths, final List changeSets) { ChangeSet result = null; // We need to search from latest to earliest, otherwise an incorrect result is produced @@ -198,7 +199,7 @@ static ChangeSet findLatestUncloakedChangeset(final Collection cloakedPa lastChangeSetNumber = changeSetNumber; final Collection changes = s.getAffectedPaths(); - final boolean fullyCloaked = isChangesetFullyCloaked(changes, cloakedPaths); + final boolean fullyCloaked = isChangesetFullyCloaked(changes, cloakedPaths, mappedPaths); if (!fullyCloaked) { result = s; break; @@ -215,18 +216,18 @@ static ChangeSet findLatestUncloakedChangeset(final Collection cloakedPa * changesets that are fully covered by one or more of these paths * @return a list of change sets */ - public List getDetailedHistoryWithoutCloakedPaths(final Calendar fromTimestamp, final Calendar toTimestamp, final Collection cloakedPaths) { + public List getDetailedHistoryWithoutCloakedPaths(final Calendar fromTimestamp, final Calendar toTimestamp, final Collection cloakedPaths, final Collection mappedPaths) { final DateVersionSpec fromVersion = new DateVersionSpec(fromTimestamp); final DateVersionSpec toVersion = new DateVersionSpec(toTimestamp); - return getDetailedHistoryWithoutCloakedPaths(fromVersion, toVersion, cloakedPaths); + return getDetailedHistoryWithoutCloakedPaths(fromVersion, toVersion, cloakedPaths, mappedPaths); } - public List getDetailedHistoryWithoutCloakedPaths(final VersionSpec fromVersion, final VersionSpec toVersion, final Collection cloakedPaths) { + public List getDetailedHistoryWithoutCloakedPaths(final VersionSpec fromVersion, final VersionSpec toVersion, final Collection cloakedPaths, final Collection mappedPaths) { final List changeSets = getVCCHistory(fromVersion, toVersion, true, Integer.MAX_VALUE); final ArrayList changeSetNoCloaked = new ArrayList(); for (final ChangeSet changeset : changeSets) { final Collection affectedPaths = changeset.getAffectedPaths(); - final boolean fullyCloaked = isChangesetFullyCloaked(affectedPaths, cloakedPaths); + final boolean fullyCloaked = isChangesetFullyCloaked(affectedPaths, cloakedPaths, mappedPaths); if (!fullyCloaked) { changeSetNoCloaked.add(changeset); } @@ -234,19 +235,32 @@ public List getDetailedHistoryWithoutCloakedPaths(final VersionSpec f return changeSetNoCloaked; } - static boolean isChangesetFullyCloaked(final Collection changesetPaths, final Collection cloakedPaths) { + static boolean isChangesetFullyCloaked(final Collection changesetPaths, final Collection cloakedPaths, final Collection mappedPaths) { if (cloakedPaths == null) { return false; } for (final String tfsPath : changesetPaths) { - boolean isPathCloaked = false; + String mostSpecificCloakedPath = null; for (final String cloakedPath : cloakedPaths) { if (tfsPath.regionMatches(true, 0, cloakedPath, 0, cloakedPath.length())) { - isPathCloaked = true; - break; + + if ((mostSpecificCloakedPath == null) || (mostSpecificCloakedPath.length() < cloakedPath.length())) { + mostSpecificCloakedPath = cloakedPath; + } } } - if (!isPathCloaked) { + + if (mostSpecificCloakedPath != null) { + if (mappedPaths != null) { + for (final String mappedPath : mappedPaths) { + if ((mappedPath.regionMatches(true, 0, mostSpecificCloakedPath, 0, mostSpecificCloakedPath.length())) && + (tfsPath.regionMatches(true, 0, mappedPath, 0, mappedPath.length()))) { + return false; + } + } + } + } + else { return false; } } diff --git a/tfs/src/main/java/hudson/plugins/tfs/model/WorkspaceConfiguration.java b/tfs/src/main/java/hudson/plugins/tfs/model/WorkspaceConfiguration.java index 270ab0f83..9ef0d2942 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/model/WorkspaceConfiguration.java +++ b/tfs/src/main/java/hudson/plugins/tfs/model/WorkspaceConfiguration.java @@ -3,6 +3,9 @@ import java.io.Serializable; import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; import hudson.model.InvisibleAction; @@ -21,14 +24,16 @@ public class WorkspaceConfiguration extends InvisibleAction implements Serializa private final String serverUrl; private boolean workspaceExists; private Collection cloakedPaths; + private Map mappedPaths; - public WorkspaceConfiguration(String serverUrl, String workspaceName, String projectPath, Collection cloakedPaths, String workfolder) { + public WorkspaceConfiguration(String serverUrl, String workspaceName, String projectPath, Collection cloakedPaths, Map mappedPaths, String workfolder) { this.workspaceName = workspaceName; this.workfolder = workfolder; this.projectPath = projectPath; this.serverUrl = serverUrl; this.workspaceExists = true; this.cloakedPaths = cloakedPaths; + this.mappedPaths = mappedPaths; } public WorkspaceConfiguration(WorkspaceConfiguration configuration) { @@ -38,6 +43,7 @@ public WorkspaceConfiguration(WorkspaceConfiguration configuration) { this.serverUrl = configuration.serverUrl; this.workspaceExists = configuration.workspaceExists; this.cloakedPaths = configuration.cloakedPaths; + this.mappedPaths = configuration.mappedPaths; } public String getWorkspaceName() { @@ -68,6 +74,10 @@ public Collection getCloakedPaths() { return cloakedPaths; } + public Map getMappedPaths() { + return mappedPaths; + } + @Override public int hashCode() { final int prime = 31; @@ -78,6 +88,7 @@ public int hashCode() { result = prime * result + (workspaceExists ? 1231 : 1237); result = prime * result + ((workspaceName == null) ? 0 : workspaceName.hashCode()); result = prime * result + ((cloakedPaths == null) ? 0 : cloakedPaths.hashCode()); + result = prime * result + ((mappedPaths == null) ? 0 : mappedPaths.hashCode()); return result; } @@ -121,6 +132,35 @@ else if (cloakedPaths.size() != other.cloakedPaths.size()) return false; else if (!cloakedPaths.containsAll(other.cloakedPaths)) return false; + if (mappedPaths == null) { + if (other.mappedPaths != null) + return false; + } else if (other.mappedPaths == null) + return false; + else if (mappedPaths.size() != other.mappedPaths.size()) + return false; + else { + final Iterator> localPaths = mappedPaths.entrySet().iterator(); + while (localPaths.hasNext()) { + final Entry actualItem = localPaths.next(); + String key = actualItem.getKey(); + if (!other.mappedPaths.containsKey(key)) { + return false; + } + + final String expectedItem = other.mappedPaths.get(key); + final String actualMappedPath = actualItem.getValue(); + if (actualMappedPath == null) { + if (expectedItem != null) + return false; + } + else { + if (!actualMappedPath.equals(expectedItem)) { + return false; + } + } + } + } return true; } diff --git a/tfs/src/main/java/hudson/plugins/tfs/model/Workspaces.java b/tfs/src/main/java/hudson/plugins/tfs/model/Workspaces.java index 7de0ae756..4779eecf4 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/model/Workspaces.java +++ b/tfs/src/main/java/hudson/plugins/tfs/model/Workspaces.java @@ -104,8 +104,8 @@ public String getWorkspaceMapping(final String localPath) { * @param localPath the path in the local filesystem to map * @return a workspace */ - public Workspace newWorkspace(final String workspaceName, final String serverPath, Collection cloakedPaths, final String localPath) { - NewWorkspaceCommand command = new NewWorkspaceCommand(server, workspaceName, serverPath, cloakedPaths, localPath); + public Workspace newWorkspace(final String workspaceName, final String serverPath, Collection cloakedPaths, Map mappedPaths, final String localPath) { + NewWorkspaceCommand command = new NewWorkspaceCommand(server, workspaceName, serverPath, cloakedPaths, mappedPaths, localPath); server.execute(command.getCallable()); Workspace workspace = new Workspace(workspaceName); workspaces.put(workspaceName, workspace); diff --git a/tfs/src/main/resources/hudson/plugins/tfs/TeamFoundationServerScm/config.jelly b/tfs/src/main/resources/hudson/plugins/tfs/TeamFoundationServerScm/config.jelly index c5d0f9151..208090de3 100644 --- a/tfs/src/main/resources/hudson/plugins/tfs/TeamFoundationServerScm/config.jelly +++ b/tfs/src/main/resources/hudson/plugins/tfs/TeamFoundationServerScm/config.jelly @@ -38,6 +38,10 @@ + + + + diff --git a/tfs/src/main/resources/hudson/plugins/tfs/TeamFoundationServerScm/help-mappedPaths.html b/tfs/src/main/resources/hudson/plugins/tfs/TeamFoundationServerScm/help-mappedPaths.html new file mode 100644 index 000000000..9e7b628e0 --- /dev/null +++ b/tfs/src/main/resources/hudson/plugins/tfs/TeamFoundationServerScm/help-mappedPaths.html @@ -0,0 +1,56 @@ +
+

+ Mapped paths will be pulled into the local workspace during a GET + from the TFVC repository even if the parent folder is cloaked. +
+ Check-ins that contain files only in mapped folders will trigger a build, + whereas a check-in containing any path that is in a sub-folder of a cloaked folder + will not trigger a build. +

+

+ For example, suppose the Project path is $/Example/project/path, + and the repository contains the following subfolders: +

+ $/Example/project/path/A
+ $/Example/project/path/A/1
+ $/Example/project/path/A/2
+ $/Example/project/path/B
+ $/Example/project/path/B/1
+ $/Example/project/path/B/2
+ $/Example/project/path/C
+
+ Now, suppose the following paths were entered as Cloaked Paths: +
+ $/Example/project/path/A/2
+ $/Example/project/path/B
+
+ And the following paths were Mapped Paths: +
+ $/Example/project/path/B/1 : project/path/B1/1
+
+ ...then the resulting workspace on the Jenkins server would only have the following folders: +
+ [Workspace_Directory]/Example/project/path/A
+ [Workspace_Directory]/Example/project/path/A/1
+ [Workspace_Directory]/Example/project/path/C
+ [Workspace_Directory]/project/path/B/1
+
+

+

+ To continue with the example, + the following check-in will not trigger a build, + because it only contains changes under cloaked paths: +

+ $/Example/project/path/A/2/alpha.txt
+ $/Example/project/path/A/2/second.txt
+ $/Example/project/path/B/bravo.txt
+
+ ...whereas this check-in will trigger a build, + because it contains at least one path that isn't cloaked: +
+ $/Example/project/path/A/2/second.txt
+ $/Example/project/path/B/bravo.txt
+ $/Example/project/path/B/2/other.txt
+
+

+
diff --git a/tfs/src/test/java/hudson/plugins/tfs/TeamFoundationServerScmTest.java b/tfs/src/test/java/hudson/plugins/tfs/TeamFoundationServerScmTest.java index 217a6d83a..65ee79bd7 100644 --- a/tfs/src/test/java/hudson/plugins/tfs/TeamFoundationServerScmTest.java +++ b/tfs/src/test/java/hudson/plugins/tfs/TeamFoundationServerScmTest.java @@ -28,6 +28,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.TreeMap; +import java.util.Map.Entry; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; @@ -175,69 +177,189 @@ private static boolean isCloakedPathValid(final String path) { return TeamFoundationServerScm.DescriptorImpl.CLOAKED_PATHS_REGEX.matcher(path).matches(); } + @Test + public void assertMappedPathsCheckRegexWorks() { + + final String shouldNotMatch = "Mapped paths regex matched an invalid mapped path"; + assertFalse(shouldNotMatch, isMappedPathValid("tfsandbox")); + assertFalse(shouldNotMatch, isMappedPathValid("tfsandbox/with/sub/pathes")); + assertFalse(shouldNotMatch, isMappedPathValid("tfsandbox$/with/sub/pathes")); + assertFalse(shouldNotMatch, isMappedPathValid("$/tfsandbox/path1;$/tfsandbox/path2")); + assertFalse(shouldNotMatch, isMappedPathValid("$/tfsandbox/path1 ; $/tfsandbox/path2 ; $/tfsandbox/path3")); + assertFalse(shouldNotMatch, isMappedPathValid("$/foo/;$/bar/;$/baz/")); + assertFalse(shouldNotMatch, isMappedPathValid("$/foo/;\n$/bar/;\n$/baz/")); + + final String shoudMatch = "Mapped paths regex did not match a valid Mapped path"; + assertTrue(shoudMatch, isMappedPathValid("$/tfsandbox")); + assertTrue(shoudMatch, isMappedPathValid("$/tfsandbox/path with space/subpath")); + assertTrue(shoudMatch, isMappedPathValid("$/tfsandbox/with/${parameter}/path")); + assertTrue(shoudMatch, isMappedPathValid("$/foo/\n$/bar/\n$/baz/")); + assertTrue(shoudMatch, isMappedPathValid(" $/foo/ \n $/bar/ \n $/baz/ ")); + assertTrue(shoudMatch, isMappedPathValid("\n$/foo/\n\n$/bar/\n\n$/baz/\n")); + } + + private static boolean isMappedPathValid(final String path) { + return TeamFoundationServerScm.DescriptorImpl.MAPPED_PATHS_REGEX.matcher(path).matches(); + } + @Test public void serializeCloakedPathCollectionToString_one() { - final List cloakedPaths = Collections.singletonList("$/foo"); + final List pathCollection = Collections.singletonList("$/foo"); - final String actual = TeamFoundationServerScm.serializeCloakedPathCollectionToString(cloakedPaths); + final String actual = TeamFoundationServerScm.serializeCloakedPathCollectionToString(pathCollection); Assert.assertEquals("$/foo", actual); } @Test public void serializeCloakedPathCollectionToString_two() { - final List cloakedPaths = Arrays.asList("$/foo", "$/bar"); + final List pathCollection = Arrays.asList("$/foo", "$/bar"); - final String actual = TeamFoundationServerScm.serializeCloakedPathCollectionToString(cloakedPaths); + final String actual = TeamFoundationServerScm.serializeCloakedPathCollectionToString(pathCollection); Assert.assertEquals("$/foo\n$/bar", actual); } @Test public void serializeCloakedPathCollectionToString_many() { - final List cloakedPaths = Arrays.asList("$/foo/", "$/bar/", "$/baz/"); + final List pathCollection = Arrays.asList("$/foo/", "$/bar/", "$/baz/"); - final String actual = TeamFoundationServerScm.serializeCloakedPathCollectionToString(cloakedPaths); + final String actual = TeamFoundationServerScm.serializeCloakedPathCollectionToString(pathCollection); Assert.assertEquals("$/foo/\n$/bar/\n$/baz/", actual); } @Test - public void splitCloakedPaths_one() { + public void serializeMappedPathCollectionToString_one() { + final Map pathCollection = Collections.singletonMap("$/foo", "foo"); + + final String actual = TeamFoundationServerScm.serializeMappedPathCollectionToString(pathCollection); + + Assert.assertEquals("$/foo : foo", actual); + } + + @Test + public void serializeMappedPathCollectionToString_two() { + final Map pathCollection = new TreeMap(); + pathCollection.put("$/foo", "foo"); + pathCollection.put("$/bar", "bar"); + + final String actual = TeamFoundationServerScm.serializeMappedPathCollectionToString(pathCollection); + + Assert.assertEquals("$/bar : bar\n$/foo : foo", actual); + } + + @Test + public void serializeMappedPathCollectionToString_many() { + final Map pathCollection = new TreeMap(); + pathCollection.put("$/foo/", "foo"); + pathCollection.put("$/bar/", "bar"); + pathCollection.put("$/baz/", "baz"); + + final String actual = TeamFoundationServerScm.serializeMappedPathCollectionToString(pathCollection); + + Assert.assertEquals("$/bar/ : bar\n$/baz/ : baz\n$/foo/ : foo", actual); + } + + @Test + public void deserializeCloakedPathCollectionFromString_one() { final String input = "$/foo/"; - final Collection actual = TeamFoundationServerScm.splitCloakedPaths(input); + final Collection actual = TeamFoundationServerScm.deserializeCloakedPathCollectionFromString(input); areEqual(actual, input); } @Test - public void splitCloakedPaths_newlinesMany() { + public void deserializeCloakedPathCollectionFromString_newlinesMany() { final String input = "$/foo/\n$/bar/\n$/baz/"; - final Collection actual = TeamFoundationServerScm.splitCloakedPaths(input); + final Collection actual = TeamFoundationServerScm.deserializeCloakedPathCollectionFromString(input); areEqual(actual, "$/foo/", "$/bar/", "$/baz/"); } @Test - public void splitCloakedPaths_newlinesWithLiberalSpacing() { + public void deserializeCloakedPathCollectionFromString_newlinesWithLiberalSpacing() { final String input = " $/foo/ \n $/bar/ \n $/baz/ "; - final Collection actual = TeamFoundationServerScm.splitCloakedPaths(input); + final Collection actual = TeamFoundationServerScm.deserializeCloakedPathCollectionFromString(input); areEqual(actual, "$/foo/", "$/bar/", "$/baz/"); } @Test - public void splitCloakedPaths_newlinesWithBlankLines() { + public void deserializeCloakedPathCollectionFromString_newlinesWithBlankLines() { final String input = "\n$/foo/\n\n$/bar/\n\n$/baz/\n"; - final Collection actual = TeamFoundationServerScm.splitCloakedPaths(input); + final Collection actual = TeamFoundationServerScm.deserializeCloakedPathCollectionFromString(input); areEqual(actual, "$/foo/", "$/bar/", "$/baz/"); } + @Test + public void deserializeMappedPathCollectionFromString_one() { + final String input = "$/foo/:foo"; + + final Map actual = TeamFoundationServerScm.deserializeMappedPathCollectionFromString(input); + + final Map expected = new TreeMap(); + expected.put("$/foo/", "foo"); + areEqual(actual, expected); + } + + @Test + public void deserializeMappedPathCollectionFromString_newlinesMany() { + final String input = "$/foo/:foo\n$/bar/:bar\n$/baz/:baz"; + + final Map actual = TeamFoundationServerScm.deserializeMappedPathCollectionFromString(input); + + final Map expected = new TreeMap(); + expected.put("$/foo/", "foo"); + expected.put("$/bar/", "bar"); + expected.put("$/baz/", "baz"); + areEqual(actual, expected); + } + + @Test + public void deserializeMappedPathCollectionFromString_tfsPathOnly() { + final String input = " $/foo/\n $/bar/\n $/baz/ "; + + final Map actual = TeamFoundationServerScm.deserializeMappedPathCollectionFromString(input); + + final Map expected = new TreeMap(); + expected.put("$/foo/", null); + expected.put("$/bar/", null); + expected.put("$/baz/", null); + areEqual(actual, expected); + } + + @Test + public void deserializeMappedPathCollectionFromString_newlinesWithLiberalSpacing() { + final String input = " $/foo/ : foo \n $/bar/ : bar \n $/baz/ : baz "; + + final Map actual = TeamFoundationServerScm.deserializeMappedPathCollectionFromString(input); + + final Map expected = new TreeMap(); + expected.put("$/foo/", "foo"); + expected.put("$/bar/", "bar"); + expected.put("$/baz/", "baz"); + areEqual(actual, expected); + } + + @Test + public void deserializeMappedPathCollectionFromString_newlinesWithBlankLines() { + final String input = "\n$/foo/:foo\n\n$/bar/:bar\n\n$/baz/:baz\n"; + + final Map actual = TeamFoundationServerScm.deserializeMappedPathCollectionFromString(input); + + final Map expected = new TreeMap(); + expected.put("$/foo/", "foo"); + expected.put("$/bar/", "bar"); + expected.put("$/baz/", "baz"); + areEqual(actual, expected); + } + private static void areEqual(final Collection actual, T... expected) { final Iterator ai = actual.iterator(); int ei = 0; @@ -258,6 +380,33 @@ private static void areEqual(final Collection actual, T... expected) { } } } + + private static void areEqual(final Map actual, Map expected) { + if (actual.size() > expected.size()) + { + Assert.fail("There were more elements than expected"); + return; + } + + if (actual.size() < expected.size()) { + Assert.fail("Some elements were missing from actual."); + return; + } + + final Iterator> ai = actual.entrySet().iterator(); + while (ai.hasNext()) { + final Entry actualItem = ai.next(); + T key = actualItem.getKey(); + if (!expected.containsKey(key)) { + Assert.fail(); + continue; + } + + final T expectedItem = expected.get(key); + Assert.assertEquals(expectedItem, actualItem.getValue()); + } + } + @Test public void assertDefaultValueIsUsedForNullLocalPath() { TeamFoundationServerScm scm = new TeamFoundationServerScm("serverurl", "projectpath", "workspace"); diff --git a/tfs/src/test/java/hudson/plugins/tfs/actions/CheckoutActionTest.java b/tfs/src/test/java/hudson/plugins/tfs/actions/CheckoutActionTest.java index b18418f66..c8d47e2d4 100644 --- a/tfs/src/test/java/hudson/plugins/tfs/actions/CheckoutActionTest.java +++ b/tfs/src/test/java/hudson/plugins/tfs/actions/CheckoutActionTest.java @@ -8,6 +8,9 @@ import java.util.Calendar; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; import com.microsoft.tfs.core.clients.versioncontrol.specs.version.DateVersionSpec; import com.microsoft.tfs.core.clients.versioncontrol.specs.version.VersionSpec; @@ -33,6 +36,10 @@ public class CheckoutActionTest { private static final List EMPTY_CLOAKED_PATHS_LIST = Collections.emptyList(); + + private static final Map EMPTY_MAPPED_PATHS_MAP = new TreeMap(); + private static final Set EMPTY_MAPPED_PATHS_LIST = EMPTY_MAPPED_PATHS_MAP.keySet(); + private static final String MY_LABEL = "MyLabel"; private FilePath hudsonWs; private @Mock Server server; @@ -66,12 +73,12 @@ public void assertFirstCheckoutBySingleVersionSpecNotUsingUpdate() throws Except prepareCommonMocks(); when(project.getProjectPath()).thenReturn("project"); when(workspaces.exists("workspace")).thenReturn(true).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); verify(project).getFiles(isA(String.class), eq(MY_LABEL), eq(false)); verify(workspaces).deleteWorkspace(workspace); } @@ -81,12 +88,12 @@ public void assertFirstCheckoutNotUsingUpdate() throws Exception { prepareCommonMocks(); when(project.getProjectPath()).thenReturn("project"); when(workspaces.exists("workspace")).thenReturn(true).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); verify(project).getFiles(isA(String.class), eq("D2009-09-24T00:00:00Z"), eq(false)); verify(workspaces).deleteWorkspace(workspace); } @@ -96,11 +103,11 @@ public void assertFirstCheckoutBySingleVersionSpecUsingUpdate() throws Exception prepareCommonMocks(); when(project.getProjectPath()).thenReturn("project"); when(workspaces.exists(new Workspace("workspace"))).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); verify(project).getFiles(isA(String.class), eq(MY_LABEL), eq(false)); verify(workspaces, never()).deleteWorkspace(isA(Workspace.class)); } @@ -110,11 +117,11 @@ public void assertFirstCheckoutUsingUpdate() throws Exception { prepareCommonMocks(); when(project.getProjectPath()).thenReturn("project"); when(workspaces.exists(new Workspace("workspace"))).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); verify(project).getFiles(isA(String.class), eq("D2009-09-24T00:00:00Z"), eq(false)); verify(workspaces, never()).deleteWorkspace(isA(Workspace.class)); } @@ -126,10 +133,10 @@ public void assertSecondCheckoutBySingleVersionSpecUsingUpdate() throws Exceptio when(workspaces.getWorkspace("workspace")).thenReturn(workspace); when(workspace.getComputer()).thenReturn("LocalComputer"); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); verify(project).getFiles(isA(String.class), eq(MY_LABEL), eq(false)); - verify(workspaces, never()).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); + verify(workspaces, never()).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); verify(workspaces, never()).deleteWorkspace(isA(Workspace.class)); } @@ -140,10 +147,10 @@ public void assertSecondCheckoutUsingUpdate() throws Exception { when(workspaces.getWorkspace("workspace")).thenReturn(workspace); when(workspace.getComputer()).thenReturn("LocalComputer"); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); verify(project).getFiles(isA(String.class), eq("D2009-09-24T00:00:00Z"), eq(false)); - verify(workspaces, never()).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); + verify(workspaces, never()).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); verify(workspaces, never()).deleteWorkspace(isA(Workspace.class)); } @@ -152,12 +159,12 @@ public void assertSecondCheckoutBySingleVersionSpecNotUsingUpdate() throws Excep prepareCommonMocks(); when(project.getProjectPath()).thenReturn("project"); when(workspaces.exists("workspace")).thenReturn(true).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); verify(project).getFiles(isA(String.class), eq(MY_LABEL), eq(false)); verify(workspaces).deleteWorkspace(workspace); } @@ -167,12 +174,12 @@ public void assertSecondCheckoutNotUsingUpdate() throws Exception { prepareCommonMocks(); when(project.getProjectPath()).thenReturn("project"); when(workspaces.exists("workspace")).thenReturn(true).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); verify(project).getFiles(isA(String.class), eq("D2009-09-24T00:00:00Z"), eq(false)); verify(workspaces).deleteWorkspace(workspace); } @@ -184,7 +191,7 @@ public void assertDetailedHistoryIsNotRetrievedInFirstBuildCheckingOutByLabel() when(workspaces.getWorkspace("workspace")).thenReturn(workspace); when(workspace.getComputer()).thenReturn("LocalComputer"); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); verify(project, never()).getDetailedHistory(isA(Calendar.class), isA(Calendar.class)); } @@ -196,7 +203,7 @@ public void assertDetailedHistoryIsNotRetrievedInFirstBuild() throws Exception { when(workspaces.getWorkspace("workspace")).thenReturn(workspace); when(workspace.getComputer()).thenReturn("LocalComputer"); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); verify(project, never()).getDetailedHistory(isA(Calendar.class), isA(Calendar.class)); } @@ -210,7 +217,7 @@ public void assertDetailedHistoryIsRetrievedInSecondBuildCheckingOutByLabel() th when(workspace.getComputer()).thenReturn("LocalComputer"); when(project.getDetailedHistory(isA(String.class))).thenReturn(list); - CheckoutAction action = new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false); + CheckoutAction action = new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false); List actualList = action.checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); assertSame("The list from the detailed history, was not the same as returned from checkout", list, actualList); @@ -224,16 +231,16 @@ public void assertDetailedHistoryIsRetrievedInSecondBuild() throws Exception { when(workspaces.exists("workspace")).thenReturn(true); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); when(workspace.getComputer()).thenReturn("LocalComputer"); - when(project.getDetailedHistoryWithoutCloakedPaths(isA(VersionSpec.class), isA(VersionSpec.class), anyCollection())).thenReturn(list); + when(project.getDetailedHistoryWithoutCloakedPaths(isA(VersionSpec.class), isA(VersionSpec.class), anyCollection(), anyCollection())).thenReturn(list); - CheckoutAction action = new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false); + CheckoutAction action = new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false); final Calendar startDate = Util.getCalendar(2008, 9, 24); final Calendar endDate = Util.getCalendar(2008, 10, 24); List actualList = action.checkout(server, hudsonWs, startDate, endDate); assertEquals("The list from the detailed history, was not the same as returned from checkout", list, actualList); final DateVersionSpec startDateVersionSpec = new DateVersionSpec(startDate); - verify(project).getDetailedHistoryWithoutCloakedPaths(argThat(new DateVersionSpecMatcher(startDateVersionSpec)), isA(VersionSpec.class), eq(EMPTY_CLOAKED_PATHS_LIST)); + verify(project).getDetailedHistoryWithoutCloakedPaths(argThat(new DateVersionSpecMatcher(startDateVersionSpec)), isA(VersionSpec.class), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_LIST)); } @Test @@ -245,9 +252,9 @@ public void assertWorkFolderIsCleanedIfNotUsingUpdate() throws Exception { prepareCommonMocks(); when(workspaces.exists(new Workspace("workspace"))).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, "tfs-ws", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, "tfs-ws", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); assertTrue("The local folder was removed", tfsWs.exists()); assertEquals("The local TFS folder was not cleaned", 0, tfsWs.list((FileFilter)null).size()); @@ -263,9 +270,9 @@ public void assertWorkFolderIsCleanedIfNotUsingUpdateCheckingOutByLabel() throws prepareCommonMocks(); when(workspaces.exists(new Workspace("workspace"))).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, "tfs-ws", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, "tfs-ws", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); assertTrue("The local folder was removed", tfsWs.exists()); assertEquals("The local TFS folder was not cleaned", 0, tfsWs.list((FileFilter)null).size()); @@ -283,7 +290,7 @@ public void assertWorkspaceIsNotCleanedIfUsingUpdate() throws Exception { when(workspaces.getWorkspace("workspace")).thenReturn(workspace); when(workspace.getComputer()).thenReturn("LocalComputer"); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, "tfs-ws", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, "tfs-ws", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); assertTrue("The local folder was removed", tfsWs.exists()); assertEquals("The TFS workspace path was cleaned", 1, hudsonWs.list((FileFilter)null).size()); @@ -296,15 +303,15 @@ public void assertCheckoutBySingleVersionSpecDeletesWorkspaceAtStartIfNotUsingUp when(workspaces.exists("workspace")).thenReturn(true).thenReturn(false); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); when(project.getProjectPath()).thenReturn("project"); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); verify(server).getWorkspaces(); verify(workspaces, times(2)).exists("workspace"); verify(workspaces).getWorkspace("workspace"); verify(workspaces).deleteWorkspace(workspace); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); verify(workspaces).getWorkspaceMapping(anyString()); verifyNoMoreInteractions(workspaces); } @@ -316,15 +323,15 @@ public void assertCheckoutDeletesWorkspaceAtStartIfNotUsingUpdate() throws Excep when(workspaces.exists("workspace")).thenReturn(true).thenReturn(false); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); when(project.getProjectPath()).thenReturn("project"); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); verify(server).getWorkspaces(); verify(workspaces, times(2)).exists("workspace"); verify(workspaces).getWorkspace("workspace"); verify(workspaces).deleteWorkspace(workspace); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); verify(workspaces).getWorkspaceMapping(anyString()); verifyNoMoreInteractions(workspaces); } @@ -336,7 +343,7 @@ public void assertCheckoutDoesNotDeleteWorkspaceAtStartIfUsingUpdate() throws Ex when(workspaces.exists("workspace")).thenReturn(true).thenReturn(true); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); verify(server).getWorkspaces(); verify(workspaces, times(2)).exists("workspace"); @@ -352,7 +359,7 @@ public void assertCheckoutBySingleVersionSpecDoesNotDeleteWorkspaceAtStartIfUsin when(workspaces.exists("workspace")).thenReturn(true).thenReturn(true); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); verify(server).getWorkspaces(); verify(workspaces, times(2)).exists("workspace"); @@ -366,14 +373,14 @@ public void assertCheckoutBySingleVersionSpecDoesNotDeleteWorkspaceAtStartIfUsin public void assertCheckoutDoesNotDeleteWorkspaceIfNotUsingUpdateAndThereIsNoWorkspace() throws Exception { prepareCommonMocks(); when(workspaces.exists("workspace")).thenReturn(false).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); when(project.getProjectPath()).thenReturn("project"); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); verify(server).getWorkspaces(); verify(workspaces, times(2)).exists("workspace"); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); verify(workspaces).getWorkspaceMapping(anyString()); verifyNoMoreInteractions(workspaces); } @@ -383,14 +390,14 @@ public void assertCheckoutDoesNotDeleteWorkspaceIfNotUsingUpdateAndThereIsNoWork public void assertCheckoutBySingleVersionSpecDoesNotDeleteWorkspaceIfNotUsingUpdateAndThereIsNoWorkspace() throws Exception { prepareCommonMocks(); when(workspaces.exists("workspace")).thenReturn(false).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); when(project.getProjectPath()).thenReturn("project"); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); verify(server).getWorkspaces(); verify(workspaces, times(2)).exists("workspace"); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); verify(workspaces).getWorkspaceMapping(anyString()); verifyNoMoreInteractions(workspaces); } @@ -403,9 +410,9 @@ public void assertCheckoutOnlyRetrievesChangesToTheStartTimestampForCurrentBuild when(workspaces.exists("workspace")).thenReturn(true); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); when(workspace.getComputer()).thenReturn("LocalComputer"); - when(project.getDetailedHistoryWithoutCloakedPaths(isA(VersionSpec.class), isA(VersionSpec.class), anyCollection())).thenReturn(list); + when(project.getDetailedHistoryWithoutCloakedPaths(isA(VersionSpec.class), isA(VersionSpec.class), anyCollection(), anyCollection())).thenReturn(list); - CheckoutAction action = new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false); + CheckoutAction action = new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false); final Calendar startDate = Util.getCalendar(2008, 9, 24); final Calendar endDate = Util.getCalendar(2009, 9, 24); List actualList = action.checkout(server, hudsonWs, startDate, endDate); @@ -416,7 +423,8 @@ public void assertCheckoutOnlyRetrievesChangesToTheStartTimestampForCurrentBuild verify(project).getDetailedHistoryWithoutCloakedPaths( argThat(new DateVersionSpecMatcher(startDateVersionSpec)), argThat(new DateVersionSpecMatcher(endDateVersionSpec)), - eq(EMPTY_CLOAKED_PATHS_LIST)); + eq(EMPTY_CLOAKED_PATHS_LIST), + eq(EMPTY_MAPPED_PATHS_LIST)); verify(project).getFiles(isA(String.class), eq("D2009-09-24T00:00:00Z"), eq(false)); } diff --git a/tfs/src/test/java/hudson/plugins/tfs/commands/NewWorkspaceCommandTest.java b/tfs/src/test/java/hudson/plugins/tfs/commands/NewWorkspaceCommandTest.java index 7aea0ce73..736a13398 100644 --- a/tfs/src/test/java/hudson/plugins/tfs/commands/NewWorkspaceCommandTest.java +++ b/tfs/src/test/java/hudson/plugins/tfs/commands/NewWorkspaceCommandTest.java @@ -14,10 +14,13 @@ import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.TreeMap; public class NewWorkspaceCommandTest extends AbstractCallableCommandTest { private static final List EMPTY_CLOAKED_PATHS = Collections.emptyList(); + private static final Map EMPTY_MAPPED_PATHS_MAP = new TreeMap(); @Test public void assertLogging() throws Exception { when(server.getUserName()).thenReturn("snd\\user_cp"); @@ -28,7 +31,7 @@ public class NewWorkspaceCommandTest extends AbstractCallableCommandTest { isA(String.class), isA(WorkspaceLocation.class), isA(WorkspaceOptions.class))).thenReturn(null); - final NewWorkspaceCommand command = new NewWorkspaceCommand(server, "TheWorkspaceName", null, EMPTY_CLOAKED_PATHS, null) { + final NewWorkspaceCommand command = new NewWorkspaceCommand(server, "TheWorkspaceName", null, EMPTY_CLOAKED_PATHS, EMPTY_MAPPED_PATHS_MAP, null) { @Override public Server createServer() { return server; @@ -51,6 +54,7 @@ protected void updateCache(final TFSTeamProjectCollection connection) { @Test public void assertLoggingWhenAlsoMapping() throws Exception { final List cloakedPaths = Collections.singletonList("$/Stuff/Hide"); + final Map mappedPaths = new TreeMap(); when(server.getUserName()).thenReturn("snd\\user_cp"); when(vcc.createWorkspace(aryEq((WorkingFolder[]) null), @@ -60,7 +64,7 @@ protected void updateCache(final TFSTeamProjectCollection connection) { isA(String.class), isA(WorkspaceLocation.class), isA(WorkspaceOptions.class))).thenReturn(null); - final NewWorkspaceCommand command = new NewWorkspaceCommand(server, "TheWorkspaceName", "$/Stuff", cloakedPaths, "/home/jenkins/jobs/stuff/workspace") { + final NewWorkspaceCommand command = new NewWorkspaceCommand(server, "TheWorkspaceName", "$/Stuff", cloakedPaths, mappedPaths, "/home/jenkins/jobs/stuff/workspace") { @Override public Server createServer() { return server; @@ -83,7 +87,118 @@ protected void updateCache(final TFSTeamProjectCollection connection) { ); } + @Test public void assertLoggingWhenAlsoMappingAtSubLevelWithoutMapFolder() throws Exception { + final List cloakedPaths = Collections.singletonList("$/Stuff/Hide"); + final Map mappedPaths = new TreeMap(); + mappedPaths.put("$/Stuff/Hide/DoNotHide", null); + + when(server.getUserName()).thenReturn("snd\\user_cp"); + when(vcc.createWorkspace(aryEq((WorkingFolder[]) null), + isA(String.class), + isA(String.class), + isA(String.class), + isA(String.class), + isA(WorkspaceLocation.class), + isA(WorkspaceOptions.class))).thenReturn(null); + final NewWorkspaceCommand command = new NewWorkspaceCommand(server, "TheWorkspaceName", "$/Stuff", cloakedPaths, mappedPaths, "/home/jenkins/jobs/stuff/workspace") { + @Override + public Server createServer() { + return server; + } + + @Override + protected void updateCache(final TFSTeamProjectCollection connection) { + // no-op for tests + } + }; + final Callable callable = command.getCallable(); + + callable.call(); + + assertLog( + "Creating workspace 'TheWorkspaceName' owned by 'snd\\user_cp'...", + "Mapping '$/Stuff' to local folder '/home/jenkins/jobs/stuff/workspace' in workspace 'TheWorkspaceName'...", + "Cloaking '$/Stuff/Hide' in workspace 'TheWorkspaceName'...", + "Mapping '$/Stuff/Hide/DoNotHide' to local folder '/home/jenkins/jobs/stuff/workspace/Hide/DoNotHide' in workspace 'TheWorkspaceName'...", + "Created workspace 'TheWorkspaceName'." + ); + } + + @Test public void assertLoggingWhenAlsoMappingAtSubLevelWithMapFolder() throws Exception { + final List cloakedPaths = Collections.singletonList("$/Stuff/Hide"); + final Map mappedPaths = new TreeMap(); + mappedPaths.put("$/Stuff/Hide/DoNotHide", "Stuff/Hide/DoNotHide"); + + when(server.getUserName()).thenReturn("snd\\user_cp"); + when(vcc.createWorkspace(aryEq((WorkingFolder[]) null), + isA(String.class), + isA(String.class), + isA(String.class), + isA(String.class), + isA(WorkspaceLocation.class), + isA(WorkspaceOptions.class))).thenReturn(null); + final NewWorkspaceCommand command = new NewWorkspaceCommand(server, "TheWorkspaceName", "$/Stuff", cloakedPaths, mappedPaths, "/home/jenkins/jobs/stuff/workspace") { + @Override + public Server createServer() { + return server; + } + + @Override + protected void updateCache(final TFSTeamProjectCollection connection) { + // no-op for tests + } + }; + final Callable callable = command.getCallable(); + + callable.call(); + + assertLog( + "Creating workspace 'TheWorkspaceName' owned by 'snd\\user_cp'...", + "Mapping '$/Stuff' to local folder '/home/jenkins/jobs/stuff/workspace' in workspace 'TheWorkspaceName'...", + "Cloaking '$/Stuff/Hide' in workspace 'TheWorkspaceName'...", + "Mapping '$/Stuff/Hide/DoNotHide' to local folder '/home/jenkins/jobs/stuff/workspace/Stuff/Hide/DoNotHide' in workspace 'TheWorkspaceName'...", + "Created workspace 'TheWorkspaceName'." + ); + } + + @Test public void assertLoggingWhenAlsoMappingAtSubLevelWithRedirectMapFolder() throws Exception { + final List cloakedPaths = Collections.singletonList("$/Stuff/Hide"); + final Map mappedPaths = new TreeMap(); + mappedPaths.put("$/Stuff/Hide/DoNotHide", "AnotherFolder"); + + when(server.getUserName()).thenReturn("snd\\user_cp"); + when(vcc.createWorkspace(aryEq((WorkingFolder[]) null), + isA(String.class), + isA(String.class), + isA(String.class), + isA(String.class), + isA(WorkspaceLocation.class), + isA(WorkspaceOptions.class))).thenReturn(null); + final NewWorkspaceCommand command = new NewWorkspaceCommand(server, "TheWorkspaceName", "$/Stuff", cloakedPaths, mappedPaths, "/home/jenkins/jobs/stuff/workspace") { + @Override + public Server createServer() { + return server; + } + + @Override + protected void updateCache(final TFSTeamProjectCollection connection) { + // no-op for tests + } + }; + final Callable callable = command.getCallable(); + + callable.call(); + + assertLog( + "Creating workspace 'TheWorkspaceName' owned by 'snd\\user_cp'...", + "Mapping '$/Stuff' to local folder '/home/jenkins/jobs/stuff/workspace' in workspace 'TheWorkspaceName'...", + "Cloaking '$/Stuff/Hide' in workspace 'TheWorkspaceName'...", + "Mapping '$/Stuff/Hide/DoNotHide' to local folder '/home/jenkins/jobs/stuff/workspace/AnotherFolder' in workspace 'TheWorkspaceName'...", + "Created workspace 'TheWorkspaceName'." + ); + } + @Override protected AbstractCallableCommand createCommand(final ServerConfigurationProvider serverConfig) { - return new NewWorkspaceCommand(serverConfig, "workspaceName", "$/serverPath", EMPTY_CLOAKED_PATHS, "local/path"); + return new NewWorkspaceCommand(serverConfig, "workspaceName", "$/serverPath", EMPTY_CLOAKED_PATHS, EMPTY_MAPPED_PATHS_MAP, "local/path"); } } diff --git a/tfs/src/test/java/hudson/plugins/tfs/model/ProjectTest.java b/tfs/src/test/java/hudson/plugins/tfs/model/ProjectTest.java index 3a3689230..c27956166 100644 --- a/tfs/src/test/java/hudson/plugins/tfs/model/ProjectTest.java +++ b/tfs/src/test/java/hudson/plugins/tfs/model/ProjectTest.java @@ -112,7 +112,41 @@ public void findLatestUncloakedChangeset_latestIsUncloaked() { final ChangeSet changeSet44 = createChangeSet(44, "$/MyProject/A/foo"); final List changeSets = Arrays.asList(changeSet44, changeSet43, changeSet42); - final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, changeSets); + final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, null, changeSets); + + Assert.assertEquals("44", actual.getVersion()); + } + + @Test + public void findLatestUncloakedChangeset_latestIsUncloakedBelowACloakedFolder() { + final List cloakedPaths = Arrays.asList("$/MyProject/A/2", "$/MyProject/B"); + final List mappedPaths = Arrays.asList("$/MyProject/A/2/foo", "$/MyProject/B/bar"); + final ChangeSet changeSet42 = createChangeSet(42, "$/MyProject/A/foo", "$/MyProject/A/bar"); + final ChangeSet changeSet43 = createChangeSet(43, "$/MyProject/A/bar"); + final ChangeSet changeSet44 = createChangeSet(44, "$/MyProject/A/foo"); + final List changeSets = Arrays.asList(changeSet44, changeSet43, changeSet42); + + final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, mappedPaths, changeSets); + + Assert.assertEquals("44", actual.getVersion()); + } + + @Test + public void findLatestUncloakedChangeset_latestIsUncloakedBelowSeveralCloakedFolders() { + final List cloakedPaths = Arrays.asList( + "$/MyProject/A/2", + "$/MyProject/A/2/foo/bar", + "$/MyProject/B"); + final List mappedPaths = Arrays.asList( + "$/MyProject/A/2/foo", + "$/MyProject/A/2/foo/bar/baz", + "$/MyProject/B/bar"); + final ChangeSet changeSet42 = createChangeSet(42, "$/MyProject/A/foo", "$/MyProject/A/bar"); + final ChangeSet changeSet43 = createChangeSet(43, "$/MyProject/A/bar"); + final ChangeSet changeSet44 = createChangeSet(44, "$/MyProject/A/foo/bar/baz/foo"); + final List changeSets = Arrays.asList(changeSet44, changeSet43, changeSet42); + + final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, mappedPaths, changeSets); Assert.assertEquals("44", actual.getVersion()); } @@ -125,7 +159,46 @@ public void findLatestUncloakedChangeset_latestIsCloaked() { final ChangeSet changeSet44 = createChangeSet(44, "$/MyProject/A/2/foo"); final List changeSets = Arrays.asList(changeSet44, changeSet43, changeSet42); - final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, changeSets); + final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, null, changeSets); + + Assert.assertEquals("43", actual.getVersion()); + } + + @Test + public void findLatestUncloakedChangeset_latestIsCloakedWithMappedFolders() { + final List cloakedPaths = Arrays.asList( + "$/MyProject/A/1", + "$/MyProject/A/2", + "$/MyProject/B"); + final List mappedPaths = Arrays.asList( + "$/MyProject/A/1/foo", + "$/MyProject/B/bar"); + final ChangeSet changeSet42 = createChangeSet(42, "$/MyProject/A/foo", "$/MyProject/A/bar"); + final ChangeSet changeSet43 = createChangeSet(43, "$/MyProject/A/bar"); + final ChangeSet changeSet44 = createChangeSet(44, "$/MyProject/A/2/foo"); + final List changeSets = Arrays.asList(changeSet44, changeSet43, changeSet42); + + final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, null, changeSets); + + Assert.assertEquals("43", actual.getVersion()); + } + + @Test + public void findLatestUncloakedChangeset_latestIsCloakedBelowSeveralMappedFolders() { + final List cloakedPaths = Arrays.asList( + "$/MyProject/A/2", + "$/MyProject/A/2/foo/bar", + "$/MyProject/B"); + final List mappedPaths = Arrays.asList( + "$/MyProject/A/2/foo", + "$/MyProject/A/2/foo/bar/baz", + "$/MyProject/B/bar"); + final ChangeSet changeSet42 = createChangeSet(42, "$/MyProject/A/foo", "$/MyProject/A/bar"); + final ChangeSet changeSet43 = createChangeSet(43, "$/MyProject/A/bar"); + final ChangeSet changeSet44 = createChangeSet(44, "$/MyProject/A/2/foo/bar/foo"); + final List changeSets = Arrays.asList(changeSet44, changeSet43, changeSet42); + + final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, mappedPaths, changeSets); Assert.assertEquals("43", actual.getVersion()); } @@ -138,7 +211,7 @@ public void findLatestUncloakedChangeset_everythingIsCloaked() { final ChangeSet changeSet44 = createChangeSet(44, "$/MyProject/A/2/foo"); final List changeSets = Arrays.asList(changeSet44, changeSet43, changeSet42); - final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, changeSets); + final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, null, changeSets); Assert.assertEquals(null, actual); } @@ -161,7 +234,7 @@ private static ChangeSet createChangeSet(final int version, final String... item public void isChangesetFullyCloaked_nullCloakedPaths() { final List changesetPaths = Arrays.asList("$/foo", "$/foo/bar.baz"); - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, null); + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, null, null); Assert.assertEquals(false, actual); } @@ -172,7 +245,7 @@ public void isChangesetFullyCloaked_independentCloakedPaths() { final List changesetPaths = Arrays.asList("$/foo", "$/foo/bar.baz"); final List cloakedPaths = Arrays.asList("$/fizz", "$/fizz/FizzBuzz.java"); - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths); + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, null); Assert.assertEquals(false, actual); } @@ -182,17 +255,39 @@ public void isChangesetFullyCloaked_caseInsensitiveCloakedPaths() { final List changesetPaths = Arrays.asList("$/foo/bar/test.baz"); final List cloakedPaths = Arrays.asList("$/fOo/bAr/"); - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths); + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, null); Assert.assertEquals(true, actual); } + @Test + public void isChangesetFullyCloaked_caseInsensitiveMappedPaths() { + final List changesetPaths = Arrays.asList("$/foo/bar/baz/test.baz"); + final List cloakedPaths = Arrays.asList("$/fOo/bAr/"); + final List mappedPaths = Arrays.asList("$/fOo/bAr/BaZ"); + + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, mappedPaths); + + Assert.assertEquals(false, actual); + } + @Test public void isChangesetFullyCloaked_cloakingChild() { final List changesetPaths = Arrays.asList("$/foo", "$/foo/bar.baz"); final List cloakedPaths = Collections.singletonList("$/foo/bar.baz"); - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths); + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, null); + + Assert.assertEquals(false, actual); + } + + @Test + public void isChangesetFullyCloaked_mappingChild() { + final List changesetPaths = Arrays.asList("$/foo/bar", "$/foo/bar/baz/fizz.buzz"); + final List cloakedPaths = Collections.singletonList("$/foo/bar"); + final List mappedPaths = Arrays.asList("$/foo/bar/baz"); + + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, mappedPaths); Assert.assertEquals(false, actual); } @@ -202,7 +297,7 @@ public void isChangesetFullyCloaked_partiallyCloakedPaths() { final List changesetPaths = Arrays.asList("$/foo", "$/bar"); final List cloakedPaths = Collections.singletonList("$/foo"); - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths); + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, null); Assert.assertEquals(false, actual); } @@ -212,7 +307,23 @@ public void isChangesetFullyCloaked_fullyCloakedPath() { final List changesetPaths = Arrays.asList("$/foo", "$/foo/bar.baz"); final List cloakedPaths = Collections.singletonList("$/foo"); - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths); + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, null); + + Assert.assertEquals(true, actual); + } + + @Test + public void isChangesetFullyCloaked_fullyCloakedPathWithMappedPaths() { + final List changesetPaths = Arrays.asList( + "$/foo", + "$/foo/bar/baz", + "$/foo/bar/baz/fizz"); + final List cloakedPaths = Arrays.asList( + "$/foo", + "$/foo/bar/baz"); + final List mappedPaths = Arrays.asList("$/foo/bar"); + + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, mappedPaths); Assert.assertEquals(true, actual); } @@ -222,7 +333,7 @@ public void isChangesetFullyCloaked_fullyCloakedPaths() { final List changesetPaths = Collections.singletonList("$/foo/bar.baz"); final List cloakedPaths = Arrays.asList("$/foo", "$/bar"); - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths); + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, null); Assert.assertEquals(true, actual); } @@ -232,7 +343,7 @@ public void isChangesetFullyCloaked_manyToMany() { final List changesetPaths = Arrays.asList("$/foo/bar.baz", "$/bar/foo.baz"); final List cloakedPaths = Arrays.asList("$/foo", "$/bar"); - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths); + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, null); Assert.assertEquals(true, actual); } diff --git a/tfs/src/test/java/hudson/plugins/tfs/model/WorkspaceConfigurationTest.java b/tfs/src/test/java/hudson/plugins/tfs/model/WorkspaceConfigurationTest.java index 7a8010c54..50121716b 100644 --- a/tfs/src/test/java/hudson/plugins/tfs/model/WorkspaceConfigurationTest.java +++ b/tfs/src/test/java/hudson/plugins/tfs/model/WorkspaceConfigurationTest.java @@ -5,25 +5,57 @@ import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.TreeMap; import org.junit.Test; public class WorkspaceConfigurationTest { private static final List EMPTY_CLOAKED_PATHS_LIST = Collections.emptyList(); + private static final Map EMPTY_MAPPING_PATHS_LIST = new TreeMap(); @Test public void assertConfigurationsEquals() { final List cloakList = Collections.singletonList("cloak"); + final Map mappings = new TreeMap(); + mappings.put("$/foo/", "foo"); + mappings.put("$/bar/", "bar"); + mappings.put("$/baz/", "baz"); - WorkspaceConfiguration one = new WorkspaceConfiguration("server", "workspace", "project", cloakList, "workfolder"); - WorkspaceConfiguration two = new WorkspaceConfiguration("server", "workspace", "project", cloakList, "workfolder"); + final Map almostSimilarMappings = new TreeMap(); + mappings.put("$/foo/", "foo/"); + mappings.put("$/bar/", "bar"); + mappings.put("$/baz/", "baz"); + + final Map nullMappings = new TreeMap(); + mappings.put("$/foo/", null); + mappings.put("$/bar/", null); + mappings.put("$/baz/", null); + + WorkspaceConfiguration one = new WorkspaceConfiguration("server", "workspace", "project", cloakList, mappings, "workfolder"); + WorkspaceConfiguration two = new WorkspaceConfiguration("server", "workspace", "project", cloakList, mappings, "workfolder"); + WorkspaceConfiguration three = new WorkspaceConfiguration("server", "workspace", "project", cloakList, almostSimilarMappings, "workfolder"); + WorkspaceConfiguration four = new WorkspaceConfiguration("server", "workspace", "project", cloakList, nullMappings, "workfolder"); + assertThat(one, is(two)); assertThat(two, is(one)); assertThat(one, is(one)); - assertThat(one, not(new WorkspaceConfiguration("aserver", "workspace", "project", cloakList, "workfolder"))); - assertThat(one, not(new WorkspaceConfiguration("server", "aworkspace", "project", cloakList, "workfolder"))); - assertThat(one, not(new WorkspaceConfiguration("server", "workspace", "aproject", cloakList, "workfolder"))); - assertThat(one, not(new WorkspaceConfiguration("server", "workspace", "project", cloakList, "aworkfolder"))); - assertThat(one, not(new WorkspaceConfiguration("server", "workspace", "project", EMPTY_CLOAKED_PATHS_LIST, "workfolder"))); + + assertThat(one, not(three)); + assertThat(one, not(four)); + + assertThat(three, is(three)); + assertThat(four, is(four)); + + assertThat(one, not(new WorkspaceConfiguration("aserver", "workspace", "project", cloakList, mappings, "workfolder"))); + assertThat(one, not(new WorkspaceConfiguration("server", "aworkspace", "project", cloakList, mappings, "workfolder"))); + assertThat(one, not(new WorkspaceConfiguration("server", "workspace", "aproject", cloakList, mappings, "workfolder"))); + assertThat(one, not(new WorkspaceConfiguration("server", "workspace", "project", cloakList, mappings, "aworkfolder"))); + + assertThat(one, not(new WorkspaceConfiguration("server", "workspace", "project", null, mappings, "workfolder"))); + assertThat(one, not(new WorkspaceConfiguration("server", "workspace", "project", EMPTY_CLOAKED_PATHS_LIST, mappings, "workfolder"))); + + assertThat(one, not(new WorkspaceConfiguration("server", "workspace", "project", cloakList, null, "workfolder"))); + assertThat(one, not(new WorkspaceConfiguration("server", "workspace", "project", cloakList, EMPTY_MAPPING_PATHS_LIST, "workfolder"))); } } diff --git a/tfs/src/test/java/hudson/plugins/tfs/model/WorkspacesTest.java b/tfs/src/test/java/hudson/plugins/tfs/model/WorkspacesTest.java index de86227ef..b57818e2a 100644 --- a/tfs/src/test/java/hudson/plugins/tfs/model/WorkspacesTest.java +++ b/tfs/src/test/java/hudson/plugins/tfs/model/WorkspacesTest.java @@ -5,6 +5,8 @@ import java.io.StringReader; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.TreeMap; import hudson.plugins.tfs.commands.ListWorkspacesCommand; @@ -21,6 +23,7 @@ public class WorkspacesTest { private static final List EMPTY_CLOAKED_PATHS_LIST = Collections.emptyList(); + private static final Map EMPTY_MAPPED_PATHS_MAP = new TreeMap(); @Mock private Server server; private ListWorkspacesCommand parser; @@ -91,7 +94,7 @@ public void assertNewWorkspaceIsAddedToMap() throws Exception { when(server.execute(isA(Callable.class))).thenReturn(null); Workspaces workspaces = new Workspaces(server); - Workspace workspace = workspaces.newWorkspace("name1", null, EMPTY_CLOAKED_PATHS_LIST, null); + Workspace workspace = workspaces.newWorkspace("name1", null, EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, null); assertNotNull("The new workspace was null", workspace); assertTrue("The workspace was reported as non existant", workspaces.exists(workspace)); } @@ -101,7 +104,7 @@ public void assertGettingNewWorkspaceIsNotRetrievingServerList() throws Exceptio when(server.execute(isA(Callable.class))).thenReturn(null); Workspaces workspaces = new Workspaces(server); - workspaces.newWorkspace("name1", null, EMPTY_CLOAKED_PATHS_LIST, null); + workspaces.newWorkspace("name1", null, EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, null); assertNotNull("The get new workspace returned null", workspaces.getWorkspace("name1")); verify(server, times(1)).execute(isA(Callable.class)); } @@ -111,7 +114,7 @@ public void assertNewWorkspaceExistsIsNotRetrievingServerList() throws Exception when(server.execute(isA(Callable.class))).thenReturn(null); Workspaces workspaces = new Workspaces(server); - Workspace workspace = workspaces.newWorkspace("name1", null, EMPTY_CLOAKED_PATHS_LIST, null); + Workspace workspace = workspaces.newWorkspace("name1", null, EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, null); assertTrue("The get new workspace did not exists", workspaces.exists(workspace)); verify(server, times(1)).execute(isA(Callable.class)); } @@ -122,7 +125,7 @@ public void assertWorkspaceIsDeletedFromMap() throws Exception { Workspaces workspaces = new Workspaces(server); // Populate the map in test object assertFalse("The workspace was reported as existant", workspaces.exists(new Workspace("name"))); - Workspace workspace = workspaces.newWorkspace("name", null, EMPTY_CLOAKED_PATHS_LIST, null); + Workspace workspace = workspaces.newWorkspace("name", null, EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, null); assertTrue("The workspace was reported as non existant", workspaces.exists(new Workspace("name"))); workspaces.deleteWorkspace(workspace); assertFalse("The workspace was reported as existant", workspaces.exists(workspace)); diff --git a/tfs/src/test/java/hudson/plugins/tfs/util/BuildWorkspaceConfigurationRetrieverTest.java b/tfs/src/test/java/hudson/plugins/tfs/util/BuildWorkspaceConfigurationRetrieverTest.java index 02c51f3dc..7765f8548 100644 --- a/tfs/src/test/java/hudson/plugins/tfs/util/BuildWorkspaceConfigurationRetrieverTest.java +++ b/tfs/src/test/java/hudson/plugins/tfs/util/BuildWorkspaceConfigurationRetrieverTest.java @@ -7,6 +7,8 @@ import java.io.IOException; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.TreeMap; import hudson.model.AbstractBuild; import hudson.model.Node; @@ -21,13 +23,14 @@ public class BuildWorkspaceConfigurationRetrieverTest { private static final List EMPTY_CLOAKED_PATHS_LIST = Collections.emptyList(); + private static final Map EMPTY_MAPPING_PATHS_LIST = new TreeMap(); @Test public void assertGetLatestConfgiurationOnNode() { AbstractBuild build = mock(AbstractBuild.class); Node node = mock(Node.class); Node needleNode = mock(Node.class); - WorkspaceConfiguration configuration = new WorkspaceConfiguration("serverUrl", "workspaceName", "projectPath", EMPTY_CLOAKED_PATHS_LIST, "workfolder"); + WorkspaceConfiguration configuration = new WorkspaceConfiguration("serverUrl", "workspaceName", "projectPath", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPING_PATHS_LIST, "workfolder"); when(build.getPreviousBuild()).thenReturn(build).thenReturn(null); when(build.getBuiltOn()).thenReturn(node, node, null); when(node.getNodeName()).thenReturn("node1", "needleNode"); @@ -98,7 +101,7 @@ public void assertSaveWorkspaceConfigurationUsesSaveOnBuild() throws IOException when(build.getPreviousBuild()).thenReturn(build); when(build.getBuiltOn()).thenReturn(node); when(node.getNodeName()).thenReturn("needleNode"); - when(build.getAction(WorkspaceConfiguration.class)).thenReturn(new WorkspaceConfiguration("serverUrl", "workspaceName", "projectPath", EMPTY_CLOAKED_PATHS_LIST, "workfolder")); + when(build.getAction(WorkspaceConfiguration.class)).thenReturn(new WorkspaceConfiguration("serverUrl", "workspaceName", "projectPath", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPING_PATHS_LIST, "workfolder")); BuildWorkspaceConfiguration configuration = new BuildWorkspaceConfigurationRetriever().getLatestForNode(node, build); assertThat( configuration.getWorkspaceName(), is("workspaceName")); @@ -113,7 +116,7 @@ public void assertGetLatestConfgiurationOnPreviousDeletedNode() { AbstractBuild build = mock(AbstractBuild.class); Node node = mock(Node.class); Node needleNode = mock(Node.class); - WorkspaceConfiguration configuration = new WorkspaceConfiguration("serverUrl", "workspaceName", "projectPath", EMPTY_CLOAKED_PATHS_LIST, "workfolder"); + WorkspaceConfiguration configuration = new WorkspaceConfiguration("serverUrl", "workspaceName", "projectPath", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPING_PATHS_LIST, "workfolder"); when(build.getPreviousBuild()).thenReturn(build).thenReturn(null); when(build.getBuiltOn()).thenReturn(null); From da8a2ae7d8049e015389ee230592ae1241131e24 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 7 Feb 2018 22:02:29 +0100 Subject: [PATCH 04/42] Update POMs and cleanup the dependencies/code (#192) --- pom.xml | 6 ++-- tfs/pom.xml | 36 ++++++++++--------- .../plugins/tfs/TeamEventsEndpoint.java | 4 +-- .../TelemetryContextInitializer.java | 3 +- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/pom.xml b/pom.xml index e44387123..baf455480 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins plugin - 2.29 + 3.2 tfs-parent @@ -11,7 +11,9 @@ Team Foundation Server Plug-in parent 5.126.0-SNAPSHOT - 1.625.1 + + 1.642.3 + 7 2.19.1 ${maven-surefire-plugin.version} diff --git a/tfs/pom.xml b/tfs/pom.xml index 3b6a0b1df..6cd5af2c0 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -11,6 +11,12 @@ Team Foundation Server Plug-in 5.126.0-SNAPSHOT http://wiki.jenkins-ci.org/display/JENKINS/Team+Foundation+Server+Plugin + + + + false + + MIT licenseAll source code is under the MIT license. @@ -175,23 +181,6 @@ - - - org.jenkins-ci.main - jenkins-war - ${jenkins.version} - war - test - - - org.jenkins-ci.main - jenkins-core - ${jenkins.version} - provided - @@ -216,6 +205,19 @@ org.jenkins-ci.plugins.workflow workflow-cps 2.30 + + + + org.jenkins-ci + annotation-indexer + + + + + + org.jenkins-ci.plugins.workflow + workflow-support + 2.14 diff --git a/tfs/src/main/java/hudson/plugins/tfs/TeamEventsEndpoint.java b/tfs/src/main/java/hudson/plugins/tfs/TeamEventsEndpoint.java index fdd428dc8..3912c53f3 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/TeamEventsEndpoint.java +++ b/tfs/src/main/java/hudson/plugins/tfs/TeamEventsEndpoint.java @@ -228,7 +228,7 @@ public void doConnect( public static T findTrigger(final Job job, final Class tClass) { if (job instanceof ParameterizedJobMixIn.ParameterizedJob) { final ParameterizedJobMixIn.ParameterizedJob pJob = (ParameterizedJobMixIn.ParameterizedJob) job; - for (final Trigger trigger : pJob.getTriggers().values()) { + for (final Object trigger : pJob.getTriggers().values()) { if (tClass.isInstance(trigger)) { return tClass.cast(trigger); } @@ -242,7 +242,7 @@ public static List findTriggers(final Job job, fina List triggerList = new ArrayList<>(); if (job instanceof ParameterizedJobMixIn.ParameterizedJob) { final ParameterizedJobMixIn.ParameterizedJob pJob = (ParameterizedJobMixIn.ParameterizedJob) job; - for (final Trigger trigger : pJob.getTriggers().values()) { + for (final Object trigger : pJob.getTriggers().values()) { if (tClass.isInstance(trigger)) { triggerList.add(tClass.cast(trigger)); } diff --git a/tfs/src/main/java/hudson/plugins/tfs/telemetry/TelemetryContextInitializer.java b/tfs/src/main/java/hudson/plugins/tfs/telemetry/TelemetryContextInitializer.java index c912d00d8..4c5c84401 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/telemetry/TelemetryContextInitializer.java +++ b/tfs/src/main/java/hudson/plugins/tfs/telemetry/TelemetryContextInitializer.java @@ -111,7 +111,8 @@ private String getUserId() { final String userName = getSystemProperty(SYS_PROP_USER_NAME); final String fakeUserId = MessageFormat.format("{0}@{1}", userName, computerName); - return DigestUtils.sha1Hex(fakeUserId); + //FIXME: was sha1Hex, but this is available only in commons codec 1.7. TFS SDK 14.0.3 bundles older version + return DigestUtils.shaHex(fakeUserId); } private String getComputerName() { From d87876e8ddbe45fef2525817de673f4d7102a9cb Mon Sep 17 00:00:00 2001 From: kelliejos <31480321+kelliejos@users.noreply.github.com> Date: Thu, 22 Mar 2018 15:44:36 -0400 Subject: [PATCH 05/42] Revert "Allow users to map sub-folders of cloaked paths (#181)" (#195) * Revert "Allow users to map sub-folders of cloaked paths (#181)" This reverts commit f6fe6de50a52e4c0fe3419b996bdc8204c912210. Reverting until we can ensure that polling has not been affected and we can fix teh functional tests. * Fix checkstyle bug --- .../plugins/tfs/TeamFoundationServerScm.java | 129 +------------ .../plugins/tfs/actions/CheckoutAction.java | 9 +- .../tfs/commands/NewWorkspaceCommand.java | 32 +--- .../hudson/plugins/tfs/model/Project.java | 40 ++-- .../tfs/model/WorkspaceConfiguration.java | 42 +---- .../hudson/plugins/tfs/model/Workspaces.java | 4 +- .../plugins/tfs/util/KeyValueTextReader.java | 2 +- .../tfs/TeamFoundationServerScm/config.jelly | 4 - .../help-mappedPaths.html | 56 ------ .../tfs/TeamFoundationServerScmTest.java | 177 ++---------------- .../tfs/actions/CheckoutActionTest.java | 108 +++++------ .../tfs/commands/NewWorkspaceCommandTest.java | 121 +----------- .../hudson/plugins/tfs/model/ProjectTest.java | 133 ++----------- .../tfs/model/WorkspaceConfigurationTest.java | 46 +---- .../plugins/tfs/model/WorkspacesTest.java | 11 +- ...ldWorkspaceConfigurationRetrieverTest.java | 9 +- 16 files changed, 121 insertions(+), 802 deletions(-) delete mode 100644 tfs/src/main/resources/hudson/plugins/tfs/TeamFoundationServerScm/help-mappedPaths.html diff --git a/tfs/src/main/java/hudson/plugins/tfs/TeamFoundationServerScm.java b/tfs/src/main/java/hudson/plugins/tfs/TeamFoundationServerScm.java index 82adc55b1..86f684668 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/TeamFoundationServerScm.java +++ b/tfs/src/main/java/hudson/plugins/tfs/TeamFoundationServerScm.java @@ -1,4 +1,3 @@ -//CHECKSTYLE:OFF package hudson.plugins.tfs; import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; @@ -58,12 +57,8 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.TreeMap; -import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; @@ -90,7 +85,6 @@ public class TeamFoundationServerScm extends SCM { private final String serverUrl; private final String projectPath; private Collection cloakedPaths; - private Map mappedPaths; private String localPath; private final String workspaceName; @Deprecated private String userPassword; @@ -236,16 +230,7 @@ public String getCloakedPaths() { @DataBoundSetter public void setCloakedPaths(final String cloakedPaths) { - this.cloakedPaths = deserializeCloakedPathCollectionFromString(cloakedPaths); - } - - public String getMappedPaths() { - return serializeMappedPathCollectionToString(this.mappedPaths); - } - - @DataBoundSetter - public void setMappedPaths(final String mappedPaths) { - this.mappedPaths = deserializeMappedPathCollectionFromString(mappedPaths); + this.cloakedPaths = splitCloakedPaths(cloakedPaths); } // Bean properties END @@ -254,32 +239,6 @@ static String serializeCloakedPathCollectionToString(final Collection cl return cloakedPaths == null ? StringUtils.EMPTY : StringUtils.join(cloakedPaths, "\n"); } - static String serializeMappedPathCollectionToString(final Map mappedPaths) { - if (mappedPaths == null) { - return StringUtils.EMPTY; - } - - Map sortedPaths = new TreeMap(mappedPaths); - Iterator> iter = sortedPaths.entrySet().iterator(); - - StringBuilder sb = new StringBuilder(); - while (iter.hasNext()) { - Entry entry = iter.next(); - sb.append(entry.getKey()); - - final String mappedPath = entry.getValue(); - if (mappedPath != null) { - sb.append(' ').append(':').append(' '); - sb.append(entry.getValue()); - } - if (iter.hasNext()) { - sb.append('\n'); - } - } - - return sb.toString(); - } - String getWorkspaceName(final Run build, final Computer computer) { normalizedWorkspaceName = workspaceName; if (build != null) { @@ -315,29 +274,6 @@ Collection getCloakedPaths(final Run run) { return paths; } - Map getMappedPaths(final Run run) { - final Map paths = new TreeMap(); - if (mappedPaths != null) { - final BuildVariableResolver resolver = new BuildVariableResolver(run.getParent()); - - Iterator> iter = mappedPaths.entrySet().iterator(); - while (iter.hasNext()) { - Entry entry = iter.next(); - final String serverPath = Util.replaceMacro(substituteBuildParameter(run, entry.getKey()), resolver); - - String localPath = null; - String storedLocalPath = entry.getValue(); - if (storedLocalPath != null) { - localPath = Util.replaceMacro(substituteBuildParameter(run, storedLocalPath), resolver); - } - - paths.put(serverPath, localPath); - } - } - - return paths; - } - private String substituteBuildParameter(final Run run, final String text) { if (run instanceof AbstractBuild) { AbstractBuild build = (AbstractBuild) run; @@ -348,7 +284,7 @@ private String substituteBuildParameter(final Run run, final String text) return text; } - static Collection deserializeCloakedPathCollectionFromString(final String cloakedPaths) { + static Collection splitCloakedPaths(final String cloakedPaths) { final List cloakedPathsList = new ArrayList(); if (cloakedPaths != null && cloakedPaths.length() > 0) { final StringBuilder cloakedPath = new StringBuilder(cloakedPaths.length()); @@ -372,53 +308,11 @@ static Collection deserializeCloakedPathCollectionFromString(final Strin return cloakedPathsList; } - static Map deserializeMappedPathCollectionFromString(final String mappedPaths){ - final Map mappedPathsCollection = new HashMap(); - if (mappedPaths != null && mappedPaths.length() > 0) { - final StringBuilder path = new StringBuilder(mappedPaths.length()); - for (final char character : mappedPaths.toCharArray()) { - switch (character) { - case '\n': - if (path.length() > 0) { - String[] combinedPaths = splitMappedPath(path.toString()); - String localPath = null; - if (combinedPaths.length > 1) { - localPath = combinedPaths[1].trim(); - } - mappedPathsCollection.put(combinedPaths[0].trim(), localPath); - - path.setLength(0); - } - break; - default: - path.append(character); - break; - } - } - - if (path.length() > 0) { - String[] combinedPaths = splitMappedPath(path.toString()); - - final String serverPath = combinedPaths[0].trim(); - String localPath = null; - if (combinedPaths.length > 1) { - localPath = combinedPaths[1].trim(); - } - mappedPathsCollection.put(serverPath, localPath); - } - } - return mappedPathsCollection; - } - - static String[] splitMappedPath(final String mappedPath) { - return mappedPath.split(":", 2); - } - @Override public void checkout(final Run build, final Launcher launcher, final FilePath workspaceFilePath, final TaskListener listener, final File changelogFile, final SCMRevisionState baseline) throws IOException, InterruptedException { Server server = createServer(launcher, listener, build); try { - WorkspaceConfiguration workspaceConfiguration = new WorkspaceConfiguration(server.getUrl(), getWorkspaceName(build, workspaceFilePath.toComputer()), getProjectPath(build), getCloakedPaths(build), getMappedPaths(build), getLocalPath()); + WorkspaceConfiguration workspaceConfiguration = new WorkspaceConfiguration(server.getUrl(), getWorkspaceName(build, workspaceFilePath.toComputer()), getProjectPath(build), getCloakedPaths(build), getLocalPath()); final Run previousBuild = build.getPreviousBuild(); // Check if the configuration has changed if (previousBuild != null) { @@ -448,7 +342,7 @@ public void checkout(final Run build, final Launcher launcher, final FileP final Project project = server.getProject(projPath); final int changeSet = recordWorkspaceChangesetVersion(build, listener, project, projPath, singleVersionSpec); - CheckoutAction action = new CheckoutAction(workspaceConfiguration.getWorkspaceName(), workspaceConfiguration.getProjectPath(), workspaceConfiguration.getCloakedPaths(), workspaceConfiguration.getMappedPaths(), workspaceConfiguration.getWorkfolder(), isUseUpdate(), isUseOverwrite()); + CheckoutAction action = new CheckoutAction(workspaceConfiguration.getWorkspaceName(), workspaceConfiguration.getProjectPath(), workspaceConfiguration.getCloakedPaths(), workspaceConfiguration.getWorkfolder(), isUseUpdate(), isUseOverwrite()); List list; if (StringUtils.isNotEmpty(singleVersionSpec)) { list = action.checkoutBySingleVersionSpec(server, workspaceFilePath, singleVersionSpec); @@ -521,8 +415,7 @@ public boolean pollChanges(final AbstractProject hudsonProject, final Launcher l return (server.getProject(getProjectPath(lastRun)).getDetailedHistoryWithoutCloakedPaths( lastRun.getTimestamp(), Calendar.getInstance(), - getCloakedPaths(lastRun), - getMappedPaths(lastRun).keySet() + getCloakedPaths(lastRun) ).size() > 0); } finally { server.close(); @@ -687,7 +580,6 @@ public static class DescriptorImpl extends SCMDescriptor\\|\\*\\?]+[^\\s\\.\"/:<>\\|\\*\\?]$", Pattern.CASE_INSENSITIVE); public static final Pattern PROJECT_PATH_REGEX = Pattern.compile("^\\$\\/.*", Pattern.CASE_INSENSITIVE); public static final Pattern CLOAKED_PATHS_REGEX = Pattern.compile("\\s*\\$[^\\n;]+(\\s*[\\n]\\s*\\$[^\\n;]+){0,}\\s*", Pattern.CASE_INSENSITIVE); - public static final Pattern MAPPED_PATHS_REGEX = Pattern.compile("\\s*\\$[^\\n;]+(\\s*[\\n]\\s*\\$[^\\n;]+){0,}\\s*", Pattern.CASE_INSENSITIVE); @Override public boolean isApplicable(final Job project) { @@ -762,12 +654,6 @@ public FormValidation doWorkspaceNameCheck(@QueryParameter final String value) { public FormValidation doCloakedPathsCheck(@QueryParameter final String value) { return doRegexCheck(new Pattern[]{CLOAKED_PATHS_REGEX}, "Each cloaked path must begin with '$/'. Multiple paths must be separated by blank lines.", - null, value ); - } - - public FormValidation doMappedPathsCheck(@QueryParameter final String value) { - return doRegexCheck(new Pattern[]{MAPPED_PATHS_REGEX}, - "Each mapped path must begin with '$/'. Multiple paths must be separated by blank lines.", null, value); } @@ -828,8 +714,7 @@ public PollingResult compareRemoteRevisionWith( return (server.getProject(getProjectPath(build)).getDetailedHistoryWithoutCloakedPaths( build.getTimestamp(), Calendar.getInstance(), - getCloakedPaths(build), - getMappedPaths(build).keySet() + getCloakedPaths(build) ).size() > 0) ? PollingResult.BUILD_NOW : PollingResult.NO_CHANGES; } finally { server.close(); @@ -844,7 +729,7 @@ public PollingResult compareRemoteRevisionWith( } final Project tfsProject = server.getProject(projectPath); try { - final ChangeSet latest = tfsProject.getLatestUncloakedChangeset(tfsBaseline.changesetVersion, cloakedPaths, mappedPaths.keySet()); + final ChangeSet latest = tfsProject.getLatestUncloakedChangeset(tfsBaseline.changesetVersion, cloakedPaths); final TFSRevisionState tfsRemote = (latest != null) ? new TFSRevisionState(latest.getVersion(), projectPath) diff --git a/tfs/src/main/java/hudson/plugins/tfs/actions/CheckoutAction.java b/tfs/src/main/java/hudson/plugins/tfs/actions/CheckoutAction.java index f45acd883..a82d323b8 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/actions/CheckoutAction.java +++ b/tfs/src/main/java/hudson/plugins/tfs/actions/CheckoutAction.java @@ -20,23 +20,20 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; -import java.util.Map; public class CheckoutAction { private final String workspaceName; private final String projectPath; private final Collection cloakedPaths; - private final Map mappedPaths; private final String localFolder; private final boolean useUpdate; private final boolean useOverwrite; - public CheckoutAction(String workspaceName, String projectPath, Collection cloakedPaths, Map mappedPaths, String localFolder, boolean useUpdate, boolean useOverwrite) { + public CheckoutAction(String workspaceName, String projectPath, Collection cloakedPaths, String localFolder, boolean useUpdate, boolean useOverwrite) { this.workspaceName = workspaceName; this.projectPath = projectPath; this.cloakedPaths = cloakedPaths; - this.mappedPaths = mappedPaths; this.localFolder = localFolder; this.useUpdate = useUpdate; this.useOverwrite = useOverwrite; @@ -66,7 +63,7 @@ public List checkout(final Server server, final FilePath workspacePat project.getFiles(normalizedFolder, versionSpecString, useOverwrite); if (lastBuildVersionSpec != null) { - return project.getDetailedHistoryWithoutCloakedPaths(lastBuildVersionSpec, currentBuildVersionSpec, cloakedPaths, mappedPaths.keySet()); + return project.getDetailedHistoryWithoutCloakedPaths(lastBuildVersionSpec, currentBuildVersionSpec, cloakedPaths); } return new ArrayList(); @@ -144,7 +141,7 @@ else if (existingWorkspaceName.equalsIgnoreCase(workspaceName)) { localFolderPath.deleteContents(); } final String serverPath = project.getProjectPath(); - workspace = workspaces.newWorkspace(workspaceName, serverPath, cloakedPaths, mappedPaths, localPath); + workspace = workspaces.newWorkspace(workspaceName, serverPath, cloakedPaths, localPath); } else { workspace = workspaces.getWorkspace(workspaceName); } diff --git a/tfs/src/main/java/hudson/plugins/tfs/commands/NewWorkspaceCommand.java b/tfs/src/main/java/hudson/plugins/tfs/commands/NewWorkspaceCommand.java index b605c3dc9..8eeab2f9c 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/commands/NewWorkspaceCommand.java +++ b/tfs/src/main/java/hudson/plugins/tfs/commands/NewWorkspaceCommand.java @@ -14,17 +14,11 @@ import hudson.plugins.tfs.model.Server; import hudson.remoting.Callable; -import java.io.File; import java.io.IOException; import java.io.PrintStream; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; -import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.regex.Matcher; public class NewWorkspaceCommand extends AbstractCallableCommand { @@ -38,15 +32,13 @@ public class NewWorkspaceCommand extends AbstractCallableCommand cloakedPaths; - private final Map mappedPaths; private final String localPath; - public NewWorkspaceCommand(final ServerConfigurationProvider server, final String workspaceName, final String serverPath, Collection cloakedPaths, Map mappedPaths, final String localPath) { + public NewWorkspaceCommand(final ServerConfigurationProvider server, final String workspaceName, final String serverPath, Collection cloakedPaths, final String localPath) { super(server); this.workspaceName = workspaceName; this.serverPath = serverPath; this.cloakedPaths = cloakedPaths; - this.mappedPaths = mappedPaths; this.localPath = localPath; } @@ -67,7 +59,7 @@ public Void call() throws IOException { WorkingFolder[] foldersToMap = null; if (serverPath != null && localPath != null) { - String mappingMessage = String.format(MappingTemplate, serverPath, localPath, workspaceName); + final String mappingMessage = String.format(MappingTemplate, serverPath, localPath, workspaceName); logger.println(mappingMessage); final List folderList = new ArrayList(); @@ -81,26 +73,6 @@ public Void call() throws IOException { folderList.add(new WorkingFolder(cloakedPath, null, WorkingFolderType.CLOAK)); } - - Iterator> iter = mappedPaths.entrySet().iterator(); - while (iter.hasNext()) { - Entry entry = iter.next(); - final String mappedServerPath = entry.getKey(); - final String storedMappedLocalPath = entry.getValue(); - String mappedLocalPath = null; - if (storedMappedLocalPath != null) { - mappedLocalPath = Paths.get(localPath, storedMappedLocalPath).toString().replaceAll(Matcher.quoteReplacement(File.separator), "/"); - } - else { - final String relativePath = mappedServerPath.substring(serverPath.length()); - mappedLocalPath = Paths.get(localPath, relativePath).toString().replaceAll(Matcher.quoteReplacement(File.separator), "/"); - } - - mappingMessage = String.format(MappingTemplate, mappedServerPath, mappedLocalPath, workspaceName); - logger.println(mappingMessage); - - folderList.add(new WorkingFolder(mappedServerPath, LocalPath.canonicalize(mappedLocalPath), WorkingFolderType.MAP, RecursionType.FULL)); - } foldersToMap = folderList.toArray(EMPTY_WORKING_FOLDER_ARRAY); } diff --git a/tfs/src/main/java/hudson/plugins/tfs/model/Project.java b/tfs/src/main/java/hudson/plugins/tfs/model/Project.java index 3a7a4662c..c4062bb45 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/model/Project.java +++ b/tfs/src/main/java/hudson/plugins/tfs/model/Project.java @@ -175,17 +175,16 @@ public ChangeSet getLatestChangeset() { * Gets the latest changeset that isn't in a cloaked path. * @param fromChangeset the changeset that was last seen, as a point of reference * @param cloakedPaths the list of cloaked paths in the project - * @param mappedPaths the list of server paths that are explicitly mapped in the project * @return the {@link ChangeSet} instance representing the last entry in the history for the path */ - public ChangeSet getLatestUncloakedChangeset(final int fromChangeset, final Collection cloakedPaths, final Collection mappedPaths) { + public ChangeSet getLatestUncloakedChangeset(final int fromChangeset, final Collection cloakedPaths) { final ChangesetVersionSpec fromVersion = new ChangesetVersionSpec(fromChangeset); final List changeSets = getVCCHistory(fromVersion, LatestVersionSpec.INSTANCE, true, Integer.MAX_VALUE); - final ChangeSet result = findLatestUncloakedChangeset(cloakedPaths, mappedPaths, changeSets); + final ChangeSet result = findLatestUncloakedChangeset(cloakedPaths, changeSets); return result; } - static ChangeSet findLatestUncloakedChangeset(final Collection cloakedPaths, final Collection mappedPaths, final List changeSets) { + static ChangeSet findLatestUncloakedChangeset(final Collection cloakedPaths, final List changeSets) { ChangeSet result = null; // We need to search from latest to earliest, otherwise an incorrect result is produced @@ -199,7 +198,7 @@ static ChangeSet findLatestUncloakedChangeset(final Collection cloakedPa lastChangeSetNumber = changeSetNumber; final Collection changes = s.getAffectedPaths(); - final boolean fullyCloaked = isChangesetFullyCloaked(changes, cloakedPaths, mappedPaths); + final boolean fullyCloaked = isChangesetFullyCloaked(changes, cloakedPaths); if (!fullyCloaked) { result = s; break; @@ -216,18 +215,18 @@ static ChangeSet findLatestUncloakedChangeset(final Collection cloakedPa * changesets that are fully covered by one or more of these paths * @return a list of change sets */ - public List getDetailedHistoryWithoutCloakedPaths(final Calendar fromTimestamp, final Calendar toTimestamp, final Collection cloakedPaths, final Collection mappedPaths) { + public List getDetailedHistoryWithoutCloakedPaths(final Calendar fromTimestamp, final Calendar toTimestamp, final Collection cloakedPaths) { final DateVersionSpec fromVersion = new DateVersionSpec(fromTimestamp); final DateVersionSpec toVersion = new DateVersionSpec(toTimestamp); - return getDetailedHistoryWithoutCloakedPaths(fromVersion, toVersion, cloakedPaths, mappedPaths); + return getDetailedHistoryWithoutCloakedPaths(fromVersion, toVersion, cloakedPaths); } - public List getDetailedHistoryWithoutCloakedPaths(final VersionSpec fromVersion, final VersionSpec toVersion, final Collection cloakedPaths, final Collection mappedPaths) { + public List getDetailedHistoryWithoutCloakedPaths(final VersionSpec fromVersion, final VersionSpec toVersion, final Collection cloakedPaths) { final List changeSets = getVCCHistory(fromVersion, toVersion, true, Integer.MAX_VALUE); final ArrayList changeSetNoCloaked = new ArrayList(); for (final ChangeSet changeset : changeSets) { final Collection affectedPaths = changeset.getAffectedPaths(); - final boolean fullyCloaked = isChangesetFullyCloaked(affectedPaths, cloakedPaths, mappedPaths); + final boolean fullyCloaked = isChangesetFullyCloaked(affectedPaths, cloakedPaths); if (!fullyCloaked) { changeSetNoCloaked.add(changeset); } @@ -235,32 +234,19 @@ public List getDetailedHistoryWithoutCloakedPaths(final VersionSpec f return changeSetNoCloaked; } - static boolean isChangesetFullyCloaked(final Collection changesetPaths, final Collection cloakedPaths, final Collection mappedPaths) { + static boolean isChangesetFullyCloaked(final Collection changesetPaths, final Collection cloakedPaths) { if (cloakedPaths == null) { return false; } for (final String tfsPath : changesetPaths) { - String mostSpecificCloakedPath = null; + boolean isPathCloaked = false; for (final String cloakedPath : cloakedPaths) { if (tfsPath.regionMatches(true, 0, cloakedPath, 0, cloakedPath.length())) { - - if ((mostSpecificCloakedPath == null) || (mostSpecificCloakedPath.length() < cloakedPath.length())) { - mostSpecificCloakedPath = cloakedPath; - } + isPathCloaked = true; + break; } } - - if (mostSpecificCloakedPath != null) { - if (mappedPaths != null) { - for (final String mappedPath : mappedPaths) { - if ((mappedPath.regionMatches(true, 0, mostSpecificCloakedPath, 0, mostSpecificCloakedPath.length())) && - (tfsPath.regionMatches(true, 0, mappedPath, 0, mappedPath.length()))) { - return false; - } - } - } - } - else { + if (!isPathCloaked) { return false; } } diff --git a/tfs/src/main/java/hudson/plugins/tfs/model/WorkspaceConfiguration.java b/tfs/src/main/java/hudson/plugins/tfs/model/WorkspaceConfiguration.java index 9ef0d2942..270ab0f83 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/model/WorkspaceConfiguration.java +++ b/tfs/src/main/java/hudson/plugins/tfs/model/WorkspaceConfiguration.java @@ -3,9 +3,6 @@ import java.io.Serializable; import java.util.Collection; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; import hudson.model.InvisibleAction; @@ -24,16 +21,14 @@ public class WorkspaceConfiguration extends InvisibleAction implements Serializa private final String serverUrl; private boolean workspaceExists; private Collection cloakedPaths; - private Map mappedPaths; - public WorkspaceConfiguration(String serverUrl, String workspaceName, String projectPath, Collection cloakedPaths, Map mappedPaths, String workfolder) { + public WorkspaceConfiguration(String serverUrl, String workspaceName, String projectPath, Collection cloakedPaths, String workfolder) { this.workspaceName = workspaceName; this.workfolder = workfolder; this.projectPath = projectPath; this.serverUrl = serverUrl; this.workspaceExists = true; this.cloakedPaths = cloakedPaths; - this.mappedPaths = mappedPaths; } public WorkspaceConfiguration(WorkspaceConfiguration configuration) { @@ -43,7 +38,6 @@ public WorkspaceConfiguration(WorkspaceConfiguration configuration) { this.serverUrl = configuration.serverUrl; this.workspaceExists = configuration.workspaceExists; this.cloakedPaths = configuration.cloakedPaths; - this.mappedPaths = configuration.mappedPaths; } public String getWorkspaceName() { @@ -74,10 +68,6 @@ public Collection getCloakedPaths() { return cloakedPaths; } - public Map getMappedPaths() { - return mappedPaths; - } - @Override public int hashCode() { final int prime = 31; @@ -88,7 +78,6 @@ public int hashCode() { result = prime * result + (workspaceExists ? 1231 : 1237); result = prime * result + ((workspaceName == null) ? 0 : workspaceName.hashCode()); result = prime * result + ((cloakedPaths == null) ? 0 : cloakedPaths.hashCode()); - result = prime * result + ((mappedPaths == null) ? 0 : mappedPaths.hashCode()); return result; } @@ -132,35 +121,6 @@ else if (cloakedPaths.size() != other.cloakedPaths.size()) return false; else if (!cloakedPaths.containsAll(other.cloakedPaths)) return false; - if (mappedPaths == null) { - if (other.mappedPaths != null) - return false; - } else if (other.mappedPaths == null) - return false; - else if (mappedPaths.size() != other.mappedPaths.size()) - return false; - else { - final Iterator> localPaths = mappedPaths.entrySet().iterator(); - while (localPaths.hasNext()) { - final Entry actualItem = localPaths.next(); - String key = actualItem.getKey(); - if (!other.mappedPaths.containsKey(key)) { - return false; - } - - final String expectedItem = other.mappedPaths.get(key); - final String actualMappedPath = actualItem.getValue(); - if (actualMappedPath == null) { - if (expectedItem != null) - return false; - } - else { - if (!actualMappedPath.equals(expectedItem)) { - return false; - } - } - } - } return true; } diff --git a/tfs/src/main/java/hudson/plugins/tfs/model/Workspaces.java b/tfs/src/main/java/hudson/plugins/tfs/model/Workspaces.java index 4779eecf4..7de0ae756 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/model/Workspaces.java +++ b/tfs/src/main/java/hudson/plugins/tfs/model/Workspaces.java @@ -104,8 +104,8 @@ public String getWorkspaceMapping(final String localPath) { * @param localPath the path in the local filesystem to map * @return a workspace */ - public Workspace newWorkspace(final String workspaceName, final String serverPath, Collection cloakedPaths, Map mappedPaths, final String localPath) { - NewWorkspaceCommand command = new NewWorkspaceCommand(server, workspaceName, serverPath, cloakedPaths, mappedPaths, localPath); + public Workspace newWorkspace(final String workspaceName, final String serverPath, Collection cloakedPaths, final String localPath) { + NewWorkspaceCommand command = new NewWorkspaceCommand(server, workspaceName, serverPath, cloakedPaths, localPath); server.execute(command.getCallable()); Workspace workspace = new Workspace(workspaceName); workspaces.put(workspaceName, workspace); diff --git a/tfs/src/main/java/hudson/plugins/tfs/util/KeyValueTextReader.java b/tfs/src/main/java/hudson/plugins/tfs/util/KeyValueTextReader.java index 0819cc75a..97eb8223a 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/util/KeyValueTextReader.java +++ b/tfs/src/main/java/hudson/plugins/tfs/util/KeyValueTextReader.java @@ -14,7 +14,7 @@ * This is used when reading text files that contains keys and value pairs where the * value can be in one or several rows. First used when doing some more intelligent parsing * of the output from the tfs history command. - * + * * @author redsolo */ public class KeyValueTextReader { diff --git a/tfs/src/main/resources/hudson/plugins/tfs/TeamFoundationServerScm/config.jelly b/tfs/src/main/resources/hudson/plugins/tfs/TeamFoundationServerScm/config.jelly index 208090de3..c5d0f9151 100644 --- a/tfs/src/main/resources/hudson/plugins/tfs/TeamFoundationServerScm/config.jelly +++ b/tfs/src/main/resources/hudson/plugins/tfs/TeamFoundationServerScm/config.jelly @@ -38,10 +38,6 @@ - - - - diff --git a/tfs/src/main/resources/hudson/plugins/tfs/TeamFoundationServerScm/help-mappedPaths.html b/tfs/src/main/resources/hudson/plugins/tfs/TeamFoundationServerScm/help-mappedPaths.html deleted file mode 100644 index 9e7b628e0..000000000 --- a/tfs/src/main/resources/hudson/plugins/tfs/TeamFoundationServerScm/help-mappedPaths.html +++ /dev/null @@ -1,56 +0,0 @@ -
-

- Mapped paths will be pulled into the local workspace during a GET - from the TFVC repository even if the parent folder is cloaked. -
- Check-ins that contain files only in mapped folders will trigger a build, - whereas a check-in containing any path that is in a sub-folder of a cloaked folder - will not trigger a build. -

-

- For example, suppose the Project path is $/Example/project/path, - and the repository contains the following subfolders: -

- $/Example/project/path/A
- $/Example/project/path/A/1
- $/Example/project/path/A/2
- $/Example/project/path/B
- $/Example/project/path/B/1
- $/Example/project/path/B/2
- $/Example/project/path/C
-
- Now, suppose the following paths were entered as Cloaked Paths: -
- $/Example/project/path/A/2
- $/Example/project/path/B
-
- And the following paths were Mapped Paths: -
- $/Example/project/path/B/1 : project/path/B1/1
-
- ...then the resulting workspace on the Jenkins server would only have the following folders: -
- [Workspace_Directory]/Example/project/path/A
- [Workspace_Directory]/Example/project/path/A/1
- [Workspace_Directory]/Example/project/path/C
- [Workspace_Directory]/project/path/B/1
-
-

-

- To continue with the example, - the following check-in will not trigger a build, - because it only contains changes under cloaked paths: -

- $/Example/project/path/A/2/alpha.txt
- $/Example/project/path/A/2/second.txt
- $/Example/project/path/B/bravo.txt
-
- ...whereas this check-in will trigger a build, - because it contains at least one path that isn't cloaked: -
- $/Example/project/path/A/2/second.txt
- $/Example/project/path/B/bravo.txt
- $/Example/project/path/B/2/other.txt
-
-

-
diff --git a/tfs/src/test/java/hudson/plugins/tfs/TeamFoundationServerScmTest.java b/tfs/src/test/java/hudson/plugins/tfs/TeamFoundationServerScmTest.java index 65ee79bd7..217a6d83a 100644 --- a/tfs/src/test/java/hudson/plugins/tfs/TeamFoundationServerScmTest.java +++ b/tfs/src/test/java/hudson/plugins/tfs/TeamFoundationServerScmTest.java @@ -28,8 +28,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.TreeMap; -import java.util.Map.Entry; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; @@ -177,189 +175,69 @@ private static boolean isCloakedPathValid(final String path) { return TeamFoundationServerScm.DescriptorImpl.CLOAKED_PATHS_REGEX.matcher(path).matches(); } - @Test - public void assertMappedPathsCheckRegexWorks() { - - final String shouldNotMatch = "Mapped paths regex matched an invalid mapped path"; - assertFalse(shouldNotMatch, isMappedPathValid("tfsandbox")); - assertFalse(shouldNotMatch, isMappedPathValid("tfsandbox/with/sub/pathes")); - assertFalse(shouldNotMatch, isMappedPathValid("tfsandbox$/with/sub/pathes")); - assertFalse(shouldNotMatch, isMappedPathValid("$/tfsandbox/path1;$/tfsandbox/path2")); - assertFalse(shouldNotMatch, isMappedPathValid("$/tfsandbox/path1 ; $/tfsandbox/path2 ; $/tfsandbox/path3")); - assertFalse(shouldNotMatch, isMappedPathValid("$/foo/;$/bar/;$/baz/")); - assertFalse(shouldNotMatch, isMappedPathValid("$/foo/;\n$/bar/;\n$/baz/")); - - final String shoudMatch = "Mapped paths regex did not match a valid Mapped path"; - assertTrue(shoudMatch, isMappedPathValid("$/tfsandbox")); - assertTrue(shoudMatch, isMappedPathValid("$/tfsandbox/path with space/subpath")); - assertTrue(shoudMatch, isMappedPathValid("$/tfsandbox/with/${parameter}/path")); - assertTrue(shoudMatch, isMappedPathValid("$/foo/\n$/bar/\n$/baz/")); - assertTrue(shoudMatch, isMappedPathValid(" $/foo/ \n $/bar/ \n $/baz/ ")); - assertTrue(shoudMatch, isMappedPathValid("\n$/foo/\n\n$/bar/\n\n$/baz/\n")); - } - - private static boolean isMappedPathValid(final String path) { - return TeamFoundationServerScm.DescriptorImpl.MAPPED_PATHS_REGEX.matcher(path).matches(); - } - @Test public void serializeCloakedPathCollectionToString_one() { - final List pathCollection = Collections.singletonList("$/foo"); + final List cloakedPaths = Collections.singletonList("$/foo"); - final String actual = TeamFoundationServerScm.serializeCloakedPathCollectionToString(pathCollection); + final String actual = TeamFoundationServerScm.serializeCloakedPathCollectionToString(cloakedPaths); Assert.assertEquals("$/foo", actual); } @Test public void serializeCloakedPathCollectionToString_two() { - final List pathCollection = Arrays.asList("$/foo", "$/bar"); + final List cloakedPaths = Arrays.asList("$/foo", "$/bar"); - final String actual = TeamFoundationServerScm.serializeCloakedPathCollectionToString(pathCollection); + final String actual = TeamFoundationServerScm.serializeCloakedPathCollectionToString(cloakedPaths); Assert.assertEquals("$/foo\n$/bar", actual); } @Test public void serializeCloakedPathCollectionToString_many() { - final List pathCollection = Arrays.asList("$/foo/", "$/bar/", "$/baz/"); + final List cloakedPaths = Arrays.asList("$/foo/", "$/bar/", "$/baz/"); - final String actual = TeamFoundationServerScm.serializeCloakedPathCollectionToString(pathCollection); + final String actual = TeamFoundationServerScm.serializeCloakedPathCollectionToString(cloakedPaths); Assert.assertEquals("$/foo/\n$/bar/\n$/baz/", actual); } @Test - public void serializeMappedPathCollectionToString_one() { - final Map pathCollection = Collections.singletonMap("$/foo", "foo"); - - final String actual = TeamFoundationServerScm.serializeMappedPathCollectionToString(pathCollection); - - Assert.assertEquals("$/foo : foo", actual); - } - - @Test - public void serializeMappedPathCollectionToString_two() { - final Map pathCollection = new TreeMap(); - pathCollection.put("$/foo", "foo"); - pathCollection.put("$/bar", "bar"); - - final String actual = TeamFoundationServerScm.serializeMappedPathCollectionToString(pathCollection); - - Assert.assertEquals("$/bar : bar\n$/foo : foo", actual); - } - - @Test - public void serializeMappedPathCollectionToString_many() { - final Map pathCollection = new TreeMap(); - pathCollection.put("$/foo/", "foo"); - pathCollection.put("$/bar/", "bar"); - pathCollection.put("$/baz/", "baz"); - - final String actual = TeamFoundationServerScm.serializeMappedPathCollectionToString(pathCollection); - - Assert.assertEquals("$/bar/ : bar\n$/baz/ : baz\n$/foo/ : foo", actual); - } - - @Test - public void deserializeCloakedPathCollectionFromString_one() { + public void splitCloakedPaths_one() { final String input = "$/foo/"; - final Collection actual = TeamFoundationServerScm.deserializeCloakedPathCollectionFromString(input); + final Collection actual = TeamFoundationServerScm.splitCloakedPaths(input); areEqual(actual, input); } @Test - public void deserializeCloakedPathCollectionFromString_newlinesMany() { + public void splitCloakedPaths_newlinesMany() { final String input = "$/foo/\n$/bar/\n$/baz/"; - final Collection actual = TeamFoundationServerScm.deserializeCloakedPathCollectionFromString(input); + final Collection actual = TeamFoundationServerScm.splitCloakedPaths(input); areEqual(actual, "$/foo/", "$/bar/", "$/baz/"); } @Test - public void deserializeCloakedPathCollectionFromString_newlinesWithLiberalSpacing() { + public void splitCloakedPaths_newlinesWithLiberalSpacing() { final String input = " $/foo/ \n $/bar/ \n $/baz/ "; - final Collection actual = TeamFoundationServerScm.deserializeCloakedPathCollectionFromString(input); + final Collection actual = TeamFoundationServerScm.splitCloakedPaths(input); areEqual(actual, "$/foo/", "$/bar/", "$/baz/"); } @Test - public void deserializeCloakedPathCollectionFromString_newlinesWithBlankLines() { + public void splitCloakedPaths_newlinesWithBlankLines() { final String input = "\n$/foo/\n\n$/bar/\n\n$/baz/\n"; - final Collection actual = TeamFoundationServerScm.deserializeCloakedPathCollectionFromString(input); + final Collection actual = TeamFoundationServerScm.splitCloakedPaths(input); areEqual(actual, "$/foo/", "$/bar/", "$/baz/"); } - @Test - public void deserializeMappedPathCollectionFromString_one() { - final String input = "$/foo/:foo"; - - final Map actual = TeamFoundationServerScm.deserializeMappedPathCollectionFromString(input); - - final Map expected = new TreeMap(); - expected.put("$/foo/", "foo"); - areEqual(actual, expected); - } - - @Test - public void deserializeMappedPathCollectionFromString_newlinesMany() { - final String input = "$/foo/:foo\n$/bar/:bar\n$/baz/:baz"; - - final Map actual = TeamFoundationServerScm.deserializeMappedPathCollectionFromString(input); - - final Map expected = new TreeMap(); - expected.put("$/foo/", "foo"); - expected.put("$/bar/", "bar"); - expected.put("$/baz/", "baz"); - areEqual(actual, expected); - } - - @Test - public void deserializeMappedPathCollectionFromString_tfsPathOnly() { - final String input = " $/foo/\n $/bar/\n $/baz/ "; - - final Map actual = TeamFoundationServerScm.deserializeMappedPathCollectionFromString(input); - - final Map expected = new TreeMap(); - expected.put("$/foo/", null); - expected.put("$/bar/", null); - expected.put("$/baz/", null); - areEqual(actual, expected); - } - - @Test - public void deserializeMappedPathCollectionFromString_newlinesWithLiberalSpacing() { - final String input = " $/foo/ : foo \n $/bar/ : bar \n $/baz/ : baz "; - - final Map actual = TeamFoundationServerScm.deserializeMappedPathCollectionFromString(input); - - final Map expected = new TreeMap(); - expected.put("$/foo/", "foo"); - expected.put("$/bar/", "bar"); - expected.put("$/baz/", "baz"); - areEqual(actual, expected); - } - - @Test - public void deserializeMappedPathCollectionFromString_newlinesWithBlankLines() { - final String input = "\n$/foo/:foo\n\n$/bar/:bar\n\n$/baz/:baz\n"; - - final Map actual = TeamFoundationServerScm.deserializeMappedPathCollectionFromString(input); - - final Map expected = new TreeMap(); - expected.put("$/foo/", "foo"); - expected.put("$/bar/", "bar"); - expected.put("$/baz/", "baz"); - areEqual(actual, expected); - } - private static void areEqual(final Collection actual, T... expected) { final Iterator ai = actual.iterator(); int ei = 0; @@ -380,33 +258,6 @@ private static void areEqual(final Collection actual, T... expected) { } } } - - private static void areEqual(final Map actual, Map expected) { - if (actual.size() > expected.size()) - { - Assert.fail("There were more elements than expected"); - return; - } - - if (actual.size() < expected.size()) { - Assert.fail("Some elements were missing from actual."); - return; - } - - final Iterator> ai = actual.entrySet().iterator(); - while (ai.hasNext()) { - final Entry actualItem = ai.next(); - T key = actualItem.getKey(); - if (!expected.containsKey(key)) { - Assert.fail(); - continue; - } - - final T expectedItem = expected.get(key); - Assert.assertEquals(expectedItem, actualItem.getValue()); - } - } - @Test public void assertDefaultValueIsUsedForNullLocalPath() { TeamFoundationServerScm scm = new TeamFoundationServerScm("serverurl", "projectpath", "workspace"); diff --git a/tfs/src/test/java/hudson/plugins/tfs/actions/CheckoutActionTest.java b/tfs/src/test/java/hudson/plugins/tfs/actions/CheckoutActionTest.java index c8d47e2d4..b18418f66 100644 --- a/tfs/src/test/java/hudson/plugins/tfs/actions/CheckoutActionTest.java +++ b/tfs/src/test/java/hudson/plugins/tfs/actions/CheckoutActionTest.java @@ -8,9 +8,6 @@ import java.util.Calendar; import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; import com.microsoft.tfs.core.clients.versioncontrol.specs.version.DateVersionSpec; import com.microsoft.tfs.core.clients.versioncontrol.specs.version.VersionSpec; @@ -36,10 +33,6 @@ public class CheckoutActionTest { private static final List EMPTY_CLOAKED_PATHS_LIST = Collections.emptyList(); - - private static final Map EMPTY_MAPPED_PATHS_MAP = new TreeMap(); - private static final Set EMPTY_MAPPED_PATHS_LIST = EMPTY_MAPPED_PATHS_MAP.keySet(); - private static final String MY_LABEL = "MyLabel"; private FilePath hudsonWs; private @Mock Server server; @@ -73,12 +66,12 @@ public void assertFirstCheckoutBySingleVersionSpecNotUsingUpdate() throws Except prepareCommonMocks(); when(project.getProjectPath()).thenReturn("project"); when(workspaces.exists("workspace")).thenReturn(true).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); verify(project).getFiles(isA(String.class), eq(MY_LABEL), eq(false)); verify(workspaces).deleteWorkspace(workspace); } @@ -88,12 +81,12 @@ public void assertFirstCheckoutNotUsingUpdate() throws Exception { prepareCommonMocks(); when(project.getProjectPath()).thenReturn("project"); when(workspaces.exists("workspace")).thenReturn(true).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); verify(project).getFiles(isA(String.class), eq("D2009-09-24T00:00:00Z"), eq(false)); verify(workspaces).deleteWorkspace(workspace); } @@ -103,11 +96,11 @@ public void assertFirstCheckoutBySingleVersionSpecUsingUpdate() throws Exception prepareCommonMocks(); when(project.getProjectPath()).thenReturn("project"); when(workspaces.exists(new Workspace("workspace"))).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); verify(project).getFiles(isA(String.class), eq(MY_LABEL), eq(false)); verify(workspaces, never()).deleteWorkspace(isA(Workspace.class)); } @@ -117,11 +110,11 @@ public void assertFirstCheckoutUsingUpdate() throws Exception { prepareCommonMocks(); when(project.getProjectPath()).thenReturn("project"); when(workspaces.exists(new Workspace("workspace"))).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); verify(project).getFiles(isA(String.class), eq("D2009-09-24T00:00:00Z"), eq(false)); verify(workspaces, never()).deleteWorkspace(isA(Workspace.class)); } @@ -133,10 +126,10 @@ public void assertSecondCheckoutBySingleVersionSpecUsingUpdate() throws Exceptio when(workspaces.getWorkspace("workspace")).thenReturn(workspace); when(workspace.getComputer()).thenReturn("LocalComputer"); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); verify(project).getFiles(isA(String.class), eq(MY_LABEL), eq(false)); - verify(workspaces, never()).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); + verify(workspaces, never()).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); verify(workspaces, never()).deleteWorkspace(isA(Workspace.class)); } @@ -147,10 +140,10 @@ public void assertSecondCheckoutUsingUpdate() throws Exception { when(workspaces.getWorkspace("workspace")).thenReturn(workspace); when(workspace.getComputer()).thenReturn("LocalComputer"); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); verify(project).getFiles(isA(String.class), eq("D2009-09-24T00:00:00Z"), eq(false)); - verify(workspaces, never()).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); + verify(workspaces, never()).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); verify(workspaces, never()).deleteWorkspace(isA(Workspace.class)); } @@ -159,12 +152,12 @@ public void assertSecondCheckoutBySingleVersionSpecNotUsingUpdate() throws Excep prepareCommonMocks(); when(project.getProjectPath()).thenReturn("project"); when(workspaces.exists("workspace")).thenReturn(true).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); verify(project).getFiles(isA(String.class), eq(MY_LABEL), eq(false)); verify(workspaces).deleteWorkspace(workspace); } @@ -174,12 +167,12 @@ public void assertSecondCheckoutNotUsingUpdate() throws Exception { prepareCommonMocks(); when(project.getProjectPath()).thenReturn("project"); when(workspaces.exists("workspace")).thenReturn(true).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); verify(project).getFiles(isA(String.class), eq("D2009-09-24T00:00:00Z"), eq(false)); verify(workspaces).deleteWorkspace(workspace); } @@ -191,7 +184,7 @@ public void assertDetailedHistoryIsNotRetrievedInFirstBuildCheckingOutByLabel() when(workspaces.getWorkspace("workspace")).thenReturn(workspace); when(workspace.getComputer()).thenReturn("LocalComputer"); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); verify(project, never()).getDetailedHistory(isA(Calendar.class), isA(Calendar.class)); } @@ -203,7 +196,7 @@ public void assertDetailedHistoryIsNotRetrievedInFirstBuild() throws Exception { when(workspaces.getWorkspace("workspace")).thenReturn(workspace); when(workspace.getComputer()).thenReturn("LocalComputer"); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); verify(project, never()).getDetailedHistory(isA(Calendar.class), isA(Calendar.class)); } @@ -217,7 +210,7 @@ public void assertDetailedHistoryIsRetrievedInSecondBuildCheckingOutByLabel() th when(workspace.getComputer()).thenReturn("LocalComputer"); when(project.getDetailedHistory(isA(String.class))).thenReturn(list); - CheckoutAction action = new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false); + CheckoutAction action = new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false); List actualList = action.checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); assertSame("The list from the detailed history, was not the same as returned from checkout", list, actualList); @@ -231,16 +224,16 @@ public void assertDetailedHistoryIsRetrievedInSecondBuild() throws Exception { when(workspaces.exists("workspace")).thenReturn(true); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); when(workspace.getComputer()).thenReturn("LocalComputer"); - when(project.getDetailedHistoryWithoutCloakedPaths(isA(VersionSpec.class), isA(VersionSpec.class), anyCollection(), anyCollection())).thenReturn(list); + when(project.getDetailedHistoryWithoutCloakedPaths(isA(VersionSpec.class), isA(VersionSpec.class), anyCollection())).thenReturn(list); - CheckoutAction action = new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false); + CheckoutAction action = new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false); final Calendar startDate = Util.getCalendar(2008, 9, 24); final Calendar endDate = Util.getCalendar(2008, 10, 24); List actualList = action.checkout(server, hudsonWs, startDate, endDate); assertEquals("The list from the detailed history, was not the same as returned from checkout", list, actualList); final DateVersionSpec startDateVersionSpec = new DateVersionSpec(startDate); - verify(project).getDetailedHistoryWithoutCloakedPaths(argThat(new DateVersionSpecMatcher(startDateVersionSpec)), isA(VersionSpec.class), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_LIST)); + verify(project).getDetailedHistoryWithoutCloakedPaths(argThat(new DateVersionSpecMatcher(startDateVersionSpec)), isA(VersionSpec.class), eq(EMPTY_CLOAKED_PATHS_LIST)); } @Test @@ -252,9 +245,9 @@ public void assertWorkFolderIsCleanedIfNotUsingUpdate() throws Exception { prepareCommonMocks(); when(workspaces.exists(new Workspace("workspace"))).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, "tfs-ws", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, "tfs-ws", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); assertTrue("The local folder was removed", tfsWs.exists()); assertEquals("The local TFS folder was not cleaned", 0, tfsWs.list((FileFilter)null).size()); @@ -270,9 +263,9 @@ public void assertWorkFolderIsCleanedIfNotUsingUpdateCheckingOutByLabel() throws prepareCommonMocks(); when(workspaces.exists(new Workspace("workspace"))).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, "tfs-ws", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, "tfs-ws", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); assertTrue("The local folder was removed", tfsWs.exists()); assertEquals("The local TFS folder was not cleaned", 0, tfsWs.list((FileFilter)null).size()); @@ -290,7 +283,7 @@ public void assertWorkspaceIsNotCleanedIfUsingUpdate() throws Exception { when(workspaces.getWorkspace("workspace")).thenReturn(workspace); when(workspace.getComputer()).thenReturn("LocalComputer"); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, "tfs-ws", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, "tfs-ws", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); assertTrue("The local folder was removed", tfsWs.exists()); assertEquals("The TFS workspace path was cleaned", 1, hudsonWs.list((FileFilter)null).size()); @@ -303,15 +296,15 @@ public void assertCheckoutBySingleVersionSpecDeletesWorkspaceAtStartIfNotUsingUp when(workspaces.exists("workspace")).thenReturn(true).thenReturn(false); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); when(project.getProjectPath()).thenReturn("project"); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); verify(server).getWorkspaces(); verify(workspaces, times(2)).exists("workspace"); verify(workspaces).getWorkspace("workspace"); verify(workspaces).deleteWorkspace(workspace); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); verify(workspaces).getWorkspaceMapping(anyString()); verifyNoMoreInteractions(workspaces); } @@ -323,15 +316,15 @@ public void assertCheckoutDeletesWorkspaceAtStartIfNotUsingUpdate() throws Excep when(workspaces.exists("workspace")).thenReturn(true).thenReturn(false); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); when(project.getProjectPath()).thenReturn("project"); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); verify(server).getWorkspaces(); verify(workspaces, times(2)).exists("workspace"); verify(workspaces).getWorkspace("workspace"); verify(workspaces).deleteWorkspace(workspace); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); verify(workspaces).getWorkspaceMapping(anyString()); verifyNoMoreInteractions(workspaces); } @@ -343,7 +336,7 @@ public void assertCheckoutDoesNotDeleteWorkspaceAtStartIfUsingUpdate() throws Ex when(workspaces.exists("workspace")).thenReturn(true).thenReturn(true); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); verify(server).getWorkspaces(); verify(workspaces, times(2)).exists("workspace"); @@ -359,7 +352,7 @@ public void assertCheckoutBySingleVersionSpecDoesNotDeleteWorkspaceAtStartIfUsin when(workspaces.exists("workspace")).thenReturn(true).thenReturn(true); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); verify(server).getWorkspaces(); verify(workspaces, times(2)).exists("workspace"); @@ -373,14 +366,14 @@ public void assertCheckoutBySingleVersionSpecDoesNotDeleteWorkspaceAtStartIfUsin public void assertCheckoutDoesNotDeleteWorkspaceIfNotUsingUpdateAndThereIsNoWorkspace() throws Exception { prepareCommonMocks(); when(workspaces.exists("workspace")).thenReturn(false).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); when(project.getProjectPath()).thenReturn("project"); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", false, false).checkout(server, hudsonWs, null, Util.getCalendar(2009, 9, 24)); verify(server).getWorkspaces(); verify(workspaces, times(2)).exists("workspace"); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); verify(workspaces).getWorkspaceMapping(anyString()); verifyNoMoreInteractions(workspaces); } @@ -390,14 +383,14 @@ public void assertCheckoutDoesNotDeleteWorkspaceIfNotUsingUpdateAndThereIsNoWork public void assertCheckoutBySingleVersionSpecDoesNotDeleteWorkspaceIfNotUsingUpdateAndThereIsNoWorkspace() throws Exception { prepareCommonMocks(); when(workspaces.exists("workspace")).thenReturn(false).thenReturn(false); - when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class))).thenReturn(workspace); + when(workspaces.newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class))).thenReturn(workspace); when(project.getProjectPath()).thenReturn("project"); - new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); + new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", false, false).checkoutBySingleVersionSpec(server, hudsonWs, MY_LABEL); verify(server).getWorkspaces(); verify(workspaces, times(2)).exists("workspace"); - verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), eq(EMPTY_MAPPED_PATHS_MAP), isA(String.class)); + verify(workspaces).newWorkspace(eq("workspace"), eq("project"), eq(EMPTY_CLOAKED_PATHS_LIST), isA(String.class)); verify(workspaces).getWorkspaceMapping(anyString()); verifyNoMoreInteractions(workspaces); } @@ -410,9 +403,9 @@ public void assertCheckoutOnlyRetrievesChangesToTheStartTimestampForCurrentBuild when(workspaces.exists("workspace")).thenReturn(true); when(workspaces.getWorkspace("workspace")).thenReturn(workspace); when(workspace.getComputer()).thenReturn("LocalComputer"); - when(project.getDetailedHistoryWithoutCloakedPaths(isA(VersionSpec.class), isA(VersionSpec.class), anyCollection(), anyCollection())).thenReturn(list); + when(project.getDetailedHistoryWithoutCloakedPaths(isA(VersionSpec.class), isA(VersionSpec.class), anyCollection())).thenReturn(list); - CheckoutAction action = new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, ".", true, false); + CheckoutAction action = new CheckoutAction("workspace", "project", EMPTY_CLOAKED_PATHS_LIST, ".", true, false); final Calendar startDate = Util.getCalendar(2008, 9, 24); final Calendar endDate = Util.getCalendar(2009, 9, 24); List actualList = action.checkout(server, hudsonWs, startDate, endDate); @@ -423,8 +416,7 @@ public void assertCheckoutOnlyRetrievesChangesToTheStartTimestampForCurrentBuild verify(project).getDetailedHistoryWithoutCloakedPaths( argThat(new DateVersionSpecMatcher(startDateVersionSpec)), argThat(new DateVersionSpecMatcher(endDateVersionSpec)), - eq(EMPTY_CLOAKED_PATHS_LIST), - eq(EMPTY_MAPPED_PATHS_LIST)); + eq(EMPTY_CLOAKED_PATHS_LIST)); verify(project).getFiles(isA(String.class), eq("D2009-09-24T00:00:00Z"), eq(false)); } diff --git a/tfs/src/test/java/hudson/plugins/tfs/commands/NewWorkspaceCommandTest.java b/tfs/src/test/java/hudson/plugins/tfs/commands/NewWorkspaceCommandTest.java index 736a13398..7aea0ce73 100644 --- a/tfs/src/test/java/hudson/plugins/tfs/commands/NewWorkspaceCommandTest.java +++ b/tfs/src/test/java/hudson/plugins/tfs/commands/NewWorkspaceCommandTest.java @@ -14,13 +14,10 @@ import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.TreeMap; public class NewWorkspaceCommandTest extends AbstractCallableCommandTest { private static final List EMPTY_CLOAKED_PATHS = Collections.emptyList(); - private static final Map EMPTY_MAPPED_PATHS_MAP = new TreeMap(); @Test public void assertLogging() throws Exception { when(server.getUserName()).thenReturn("snd\\user_cp"); @@ -31,7 +28,7 @@ public class NewWorkspaceCommandTest extends AbstractCallableCommandTest { isA(String.class), isA(WorkspaceLocation.class), isA(WorkspaceOptions.class))).thenReturn(null); - final NewWorkspaceCommand command = new NewWorkspaceCommand(server, "TheWorkspaceName", null, EMPTY_CLOAKED_PATHS, EMPTY_MAPPED_PATHS_MAP, null) { + final NewWorkspaceCommand command = new NewWorkspaceCommand(server, "TheWorkspaceName", null, EMPTY_CLOAKED_PATHS, null) { @Override public Server createServer() { return server; @@ -54,7 +51,6 @@ protected void updateCache(final TFSTeamProjectCollection connection) { @Test public void assertLoggingWhenAlsoMapping() throws Exception { final List cloakedPaths = Collections.singletonList("$/Stuff/Hide"); - final Map mappedPaths = new TreeMap(); when(server.getUserName()).thenReturn("snd\\user_cp"); when(vcc.createWorkspace(aryEq((WorkingFolder[]) null), @@ -64,7 +60,7 @@ protected void updateCache(final TFSTeamProjectCollection connection) { isA(String.class), isA(WorkspaceLocation.class), isA(WorkspaceOptions.class))).thenReturn(null); - final NewWorkspaceCommand command = new NewWorkspaceCommand(server, "TheWorkspaceName", "$/Stuff", cloakedPaths, mappedPaths, "/home/jenkins/jobs/stuff/workspace") { + final NewWorkspaceCommand command = new NewWorkspaceCommand(server, "TheWorkspaceName", "$/Stuff", cloakedPaths, "/home/jenkins/jobs/stuff/workspace") { @Override public Server createServer() { return server; @@ -87,118 +83,7 @@ protected void updateCache(final TFSTeamProjectCollection connection) { ); } - @Test public void assertLoggingWhenAlsoMappingAtSubLevelWithoutMapFolder() throws Exception { - final List cloakedPaths = Collections.singletonList("$/Stuff/Hide"); - final Map mappedPaths = new TreeMap(); - mappedPaths.put("$/Stuff/Hide/DoNotHide", null); - - when(server.getUserName()).thenReturn("snd\\user_cp"); - when(vcc.createWorkspace(aryEq((WorkingFolder[]) null), - isA(String.class), - isA(String.class), - isA(String.class), - isA(String.class), - isA(WorkspaceLocation.class), - isA(WorkspaceOptions.class))).thenReturn(null); - final NewWorkspaceCommand command = new NewWorkspaceCommand(server, "TheWorkspaceName", "$/Stuff", cloakedPaths, mappedPaths, "/home/jenkins/jobs/stuff/workspace") { - @Override - public Server createServer() { - return server; - } - - @Override - protected void updateCache(final TFSTeamProjectCollection connection) { - // no-op for tests - } - }; - final Callable callable = command.getCallable(); - - callable.call(); - - assertLog( - "Creating workspace 'TheWorkspaceName' owned by 'snd\\user_cp'...", - "Mapping '$/Stuff' to local folder '/home/jenkins/jobs/stuff/workspace' in workspace 'TheWorkspaceName'...", - "Cloaking '$/Stuff/Hide' in workspace 'TheWorkspaceName'...", - "Mapping '$/Stuff/Hide/DoNotHide' to local folder '/home/jenkins/jobs/stuff/workspace/Hide/DoNotHide' in workspace 'TheWorkspaceName'...", - "Created workspace 'TheWorkspaceName'." - ); - } - - @Test public void assertLoggingWhenAlsoMappingAtSubLevelWithMapFolder() throws Exception { - final List cloakedPaths = Collections.singletonList("$/Stuff/Hide"); - final Map mappedPaths = new TreeMap(); - mappedPaths.put("$/Stuff/Hide/DoNotHide", "Stuff/Hide/DoNotHide"); - - when(server.getUserName()).thenReturn("snd\\user_cp"); - when(vcc.createWorkspace(aryEq((WorkingFolder[]) null), - isA(String.class), - isA(String.class), - isA(String.class), - isA(String.class), - isA(WorkspaceLocation.class), - isA(WorkspaceOptions.class))).thenReturn(null); - final NewWorkspaceCommand command = new NewWorkspaceCommand(server, "TheWorkspaceName", "$/Stuff", cloakedPaths, mappedPaths, "/home/jenkins/jobs/stuff/workspace") { - @Override - public Server createServer() { - return server; - } - - @Override - protected void updateCache(final TFSTeamProjectCollection connection) { - // no-op for tests - } - }; - final Callable callable = command.getCallable(); - - callable.call(); - - assertLog( - "Creating workspace 'TheWorkspaceName' owned by 'snd\\user_cp'...", - "Mapping '$/Stuff' to local folder '/home/jenkins/jobs/stuff/workspace' in workspace 'TheWorkspaceName'...", - "Cloaking '$/Stuff/Hide' in workspace 'TheWorkspaceName'...", - "Mapping '$/Stuff/Hide/DoNotHide' to local folder '/home/jenkins/jobs/stuff/workspace/Stuff/Hide/DoNotHide' in workspace 'TheWorkspaceName'...", - "Created workspace 'TheWorkspaceName'." - ); - } - - @Test public void assertLoggingWhenAlsoMappingAtSubLevelWithRedirectMapFolder() throws Exception { - final List cloakedPaths = Collections.singletonList("$/Stuff/Hide"); - final Map mappedPaths = new TreeMap(); - mappedPaths.put("$/Stuff/Hide/DoNotHide", "AnotherFolder"); - - when(server.getUserName()).thenReturn("snd\\user_cp"); - when(vcc.createWorkspace(aryEq((WorkingFolder[]) null), - isA(String.class), - isA(String.class), - isA(String.class), - isA(String.class), - isA(WorkspaceLocation.class), - isA(WorkspaceOptions.class))).thenReturn(null); - final NewWorkspaceCommand command = new NewWorkspaceCommand(server, "TheWorkspaceName", "$/Stuff", cloakedPaths, mappedPaths, "/home/jenkins/jobs/stuff/workspace") { - @Override - public Server createServer() { - return server; - } - - @Override - protected void updateCache(final TFSTeamProjectCollection connection) { - // no-op for tests - } - }; - final Callable callable = command.getCallable(); - - callable.call(); - - assertLog( - "Creating workspace 'TheWorkspaceName' owned by 'snd\\user_cp'...", - "Mapping '$/Stuff' to local folder '/home/jenkins/jobs/stuff/workspace' in workspace 'TheWorkspaceName'...", - "Cloaking '$/Stuff/Hide' in workspace 'TheWorkspaceName'...", - "Mapping '$/Stuff/Hide/DoNotHide' to local folder '/home/jenkins/jobs/stuff/workspace/AnotherFolder' in workspace 'TheWorkspaceName'...", - "Created workspace 'TheWorkspaceName'." - ); - } - @Override protected AbstractCallableCommand createCommand(final ServerConfigurationProvider serverConfig) { - return new NewWorkspaceCommand(serverConfig, "workspaceName", "$/serverPath", EMPTY_CLOAKED_PATHS, EMPTY_MAPPED_PATHS_MAP, "local/path"); + return new NewWorkspaceCommand(serverConfig, "workspaceName", "$/serverPath", EMPTY_CLOAKED_PATHS, "local/path"); } } diff --git a/tfs/src/test/java/hudson/plugins/tfs/model/ProjectTest.java b/tfs/src/test/java/hudson/plugins/tfs/model/ProjectTest.java index c27956166..3a3689230 100644 --- a/tfs/src/test/java/hudson/plugins/tfs/model/ProjectTest.java +++ b/tfs/src/test/java/hudson/plugins/tfs/model/ProjectTest.java @@ -112,41 +112,7 @@ public void findLatestUncloakedChangeset_latestIsUncloaked() { final ChangeSet changeSet44 = createChangeSet(44, "$/MyProject/A/foo"); final List changeSets = Arrays.asList(changeSet44, changeSet43, changeSet42); - final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, null, changeSets); - - Assert.assertEquals("44", actual.getVersion()); - } - - @Test - public void findLatestUncloakedChangeset_latestIsUncloakedBelowACloakedFolder() { - final List cloakedPaths = Arrays.asList("$/MyProject/A/2", "$/MyProject/B"); - final List mappedPaths = Arrays.asList("$/MyProject/A/2/foo", "$/MyProject/B/bar"); - final ChangeSet changeSet42 = createChangeSet(42, "$/MyProject/A/foo", "$/MyProject/A/bar"); - final ChangeSet changeSet43 = createChangeSet(43, "$/MyProject/A/bar"); - final ChangeSet changeSet44 = createChangeSet(44, "$/MyProject/A/foo"); - final List changeSets = Arrays.asList(changeSet44, changeSet43, changeSet42); - - final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, mappedPaths, changeSets); - - Assert.assertEquals("44", actual.getVersion()); - } - - @Test - public void findLatestUncloakedChangeset_latestIsUncloakedBelowSeveralCloakedFolders() { - final List cloakedPaths = Arrays.asList( - "$/MyProject/A/2", - "$/MyProject/A/2/foo/bar", - "$/MyProject/B"); - final List mappedPaths = Arrays.asList( - "$/MyProject/A/2/foo", - "$/MyProject/A/2/foo/bar/baz", - "$/MyProject/B/bar"); - final ChangeSet changeSet42 = createChangeSet(42, "$/MyProject/A/foo", "$/MyProject/A/bar"); - final ChangeSet changeSet43 = createChangeSet(43, "$/MyProject/A/bar"); - final ChangeSet changeSet44 = createChangeSet(44, "$/MyProject/A/foo/bar/baz/foo"); - final List changeSets = Arrays.asList(changeSet44, changeSet43, changeSet42); - - final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, mappedPaths, changeSets); + final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, changeSets); Assert.assertEquals("44", actual.getVersion()); } @@ -159,46 +125,7 @@ public void findLatestUncloakedChangeset_latestIsCloaked() { final ChangeSet changeSet44 = createChangeSet(44, "$/MyProject/A/2/foo"); final List changeSets = Arrays.asList(changeSet44, changeSet43, changeSet42); - final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, null, changeSets); - - Assert.assertEquals("43", actual.getVersion()); - } - - @Test - public void findLatestUncloakedChangeset_latestIsCloakedWithMappedFolders() { - final List cloakedPaths = Arrays.asList( - "$/MyProject/A/1", - "$/MyProject/A/2", - "$/MyProject/B"); - final List mappedPaths = Arrays.asList( - "$/MyProject/A/1/foo", - "$/MyProject/B/bar"); - final ChangeSet changeSet42 = createChangeSet(42, "$/MyProject/A/foo", "$/MyProject/A/bar"); - final ChangeSet changeSet43 = createChangeSet(43, "$/MyProject/A/bar"); - final ChangeSet changeSet44 = createChangeSet(44, "$/MyProject/A/2/foo"); - final List changeSets = Arrays.asList(changeSet44, changeSet43, changeSet42); - - final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, null, changeSets); - - Assert.assertEquals("43", actual.getVersion()); - } - - @Test - public void findLatestUncloakedChangeset_latestIsCloakedBelowSeveralMappedFolders() { - final List cloakedPaths = Arrays.asList( - "$/MyProject/A/2", - "$/MyProject/A/2/foo/bar", - "$/MyProject/B"); - final List mappedPaths = Arrays.asList( - "$/MyProject/A/2/foo", - "$/MyProject/A/2/foo/bar/baz", - "$/MyProject/B/bar"); - final ChangeSet changeSet42 = createChangeSet(42, "$/MyProject/A/foo", "$/MyProject/A/bar"); - final ChangeSet changeSet43 = createChangeSet(43, "$/MyProject/A/bar"); - final ChangeSet changeSet44 = createChangeSet(44, "$/MyProject/A/2/foo/bar/foo"); - final List changeSets = Arrays.asList(changeSet44, changeSet43, changeSet42); - - final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, mappedPaths, changeSets); + final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, changeSets); Assert.assertEquals("43", actual.getVersion()); } @@ -211,7 +138,7 @@ public void findLatestUncloakedChangeset_everythingIsCloaked() { final ChangeSet changeSet44 = createChangeSet(44, "$/MyProject/A/2/foo"); final List changeSets = Arrays.asList(changeSet44, changeSet43, changeSet42); - final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, null, changeSets); + final ChangeSet actual = Project.findLatestUncloakedChangeset(cloakedPaths, changeSets); Assert.assertEquals(null, actual); } @@ -234,7 +161,7 @@ private static ChangeSet createChangeSet(final int version, final String... item public void isChangesetFullyCloaked_nullCloakedPaths() { final List changesetPaths = Arrays.asList("$/foo", "$/foo/bar.baz"); - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, null, null); + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, null); Assert.assertEquals(false, actual); } @@ -245,7 +172,7 @@ public void isChangesetFullyCloaked_independentCloakedPaths() { final List changesetPaths = Arrays.asList("$/foo", "$/foo/bar.baz"); final List cloakedPaths = Arrays.asList("$/fizz", "$/fizz/FizzBuzz.java"); - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, null); + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths); Assert.assertEquals(false, actual); } @@ -255,39 +182,17 @@ public void isChangesetFullyCloaked_caseInsensitiveCloakedPaths() { final List changesetPaths = Arrays.asList("$/foo/bar/test.baz"); final List cloakedPaths = Arrays.asList("$/fOo/bAr/"); - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, null); + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths); Assert.assertEquals(true, actual); } - @Test - public void isChangesetFullyCloaked_caseInsensitiveMappedPaths() { - final List changesetPaths = Arrays.asList("$/foo/bar/baz/test.baz"); - final List cloakedPaths = Arrays.asList("$/fOo/bAr/"); - final List mappedPaths = Arrays.asList("$/fOo/bAr/BaZ"); - - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, mappedPaths); - - Assert.assertEquals(false, actual); - } - @Test public void isChangesetFullyCloaked_cloakingChild() { final List changesetPaths = Arrays.asList("$/foo", "$/foo/bar.baz"); final List cloakedPaths = Collections.singletonList("$/foo/bar.baz"); - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, null); - - Assert.assertEquals(false, actual); - } - - @Test - public void isChangesetFullyCloaked_mappingChild() { - final List changesetPaths = Arrays.asList("$/foo/bar", "$/foo/bar/baz/fizz.buzz"); - final List cloakedPaths = Collections.singletonList("$/foo/bar"); - final List mappedPaths = Arrays.asList("$/foo/bar/baz"); - - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, mappedPaths); + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths); Assert.assertEquals(false, actual); } @@ -297,7 +202,7 @@ public void isChangesetFullyCloaked_partiallyCloakedPaths() { final List changesetPaths = Arrays.asList("$/foo", "$/bar"); final List cloakedPaths = Collections.singletonList("$/foo"); - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, null); + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths); Assert.assertEquals(false, actual); } @@ -307,23 +212,7 @@ public void isChangesetFullyCloaked_fullyCloakedPath() { final List changesetPaths = Arrays.asList("$/foo", "$/foo/bar.baz"); final List cloakedPaths = Collections.singletonList("$/foo"); - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, null); - - Assert.assertEquals(true, actual); - } - - @Test - public void isChangesetFullyCloaked_fullyCloakedPathWithMappedPaths() { - final List changesetPaths = Arrays.asList( - "$/foo", - "$/foo/bar/baz", - "$/foo/bar/baz/fizz"); - final List cloakedPaths = Arrays.asList( - "$/foo", - "$/foo/bar/baz"); - final List mappedPaths = Arrays.asList("$/foo/bar"); - - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, mappedPaths); + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths); Assert.assertEquals(true, actual); } @@ -333,7 +222,7 @@ public void isChangesetFullyCloaked_fullyCloakedPaths() { final List changesetPaths = Collections.singletonList("$/foo/bar.baz"); final List cloakedPaths = Arrays.asList("$/foo", "$/bar"); - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, null); + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths); Assert.assertEquals(true, actual); } @@ -343,7 +232,7 @@ public void isChangesetFullyCloaked_manyToMany() { final List changesetPaths = Arrays.asList("$/foo/bar.baz", "$/bar/foo.baz"); final List cloakedPaths = Arrays.asList("$/foo", "$/bar"); - final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths, null); + final boolean actual = Project.isChangesetFullyCloaked(changesetPaths, cloakedPaths); Assert.assertEquals(true, actual); } diff --git a/tfs/src/test/java/hudson/plugins/tfs/model/WorkspaceConfigurationTest.java b/tfs/src/test/java/hudson/plugins/tfs/model/WorkspaceConfigurationTest.java index 50121716b..7a8010c54 100644 --- a/tfs/src/test/java/hudson/plugins/tfs/model/WorkspaceConfigurationTest.java +++ b/tfs/src/test/java/hudson/plugins/tfs/model/WorkspaceConfigurationTest.java @@ -5,57 +5,25 @@ import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.TreeMap; import org.junit.Test; public class WorkspaceConfigurationTest { private static final List EMPTY_CLOAKED_PATHS_LIST = Collections.emptyList(); - private static final Map EMPTY_MAPPING_PATHS_LIST = new TreeMap(); @Test public void assertConfigurationsEquals() { final List cloakList = Collections.singletonList("cloak"); - final Map mappings = new TreeMap(); - mappings.put("$/foo/", "foo"); - mappings.put("$/bar/", "bar"); - mappings.put("$/baz/", "baz"); - final Map almostSimilarMappings = new TreeMap(); - mappings.put("$/foo/", "foo/"); - mappings.put("$/bar/", "bar"); - mappings.put("$/baz/", "baz"); - - final Map nullMappings = new TreeMap(); - mappings.put("$/foo/", null); - mappings.put("$/bar/", null); - mappings.put("$/baz/", null); - - WorkspaceConfiguration one = new WorkspaceConfiguration("server", "workspace", "project", cloakList, mappings, "workfolder"); - WorkspaceConfiguration two = new WorkspaceConfiguration("server", "workspace", "project", cloakList, mappings, "workfolder"); - WorkspaceConfiguration three = new WorkspaceConfiguration("server", "workspace", "project", cloakList, almostSimilarMappings, "workfolder"); - WorkspaceConfiguration four = new WorkspaceConfiguration("server", "workspace", "project", cloakList, nullMappings, "workfolder"); - + WorkspaceConfiguration one = new WorkspaceConfiguration("server", "workspace", "project", cloakList, "workfolder"); + WorkspaceConfiguration two = new WorkspaceConfiguration("server", "workspace", "project", cloakList, "workfolder"); assertThat(one, is(two)); assertThat(two, is(one)); assertThat(one, is(one)); - - assertThat(one, not(three)); - assertThat(one, not(four)); - - assertThat(three, is(three)); - assertThat(four, is(four)); - - assertThat(one, not(new WorkspaceConfiguration("aserver", "workspace", "project", cloakList, mappings, "workfolder"))); - assertThat(one, not(new WorkspaceConfiguration("server", "aworkspace", "project", cloakList, mappings, "workfolder"))); - assertThat(one, not(new WorkspaceConfiguration("server", "workspace", "aproject", cloakList, mappings, "workfolder"))); - assertThat(one, not(new WorkspaceConfiguration("server", "workspace", "project", cloakList, mappings, "aworkfolder"))); - - assertThat(one, not(new WorkspaceConfiguration("server", "workspace", "project", null, mappings, "workfolder"))); - assertThat(one, not(new WorkspaceConfiguration("server", "workspace", "project", EMPTY_CLOAKED_PATHS_LIST, mappings, "workfolder"))); - - assertThat(one, not(new WorkspaceConfiguration("server", "workspace", "project", cloakList, null, "workfolder"))); - assertThat(one, not(new WorkspaceConfiguration("server", "workspace", "project", cloakList, EMPTY_MAPPING_PATHS_LIST, "workfolder"))); + assertThat(one, not(new WorkspaceConfiguration("aserver", "workspace", "project", cloakList, "workfolder"))); + assertThat(one, not(new WorkspaceConfiguration("server", "aworkspace", "project", cloakList, "workfolder"))); + assertThat(one, not(new WorkspaceConfiguration("server", "workspace", "aproject", cloakList, "workfolder"))); + assertThat(one, not(new WorkspaceConfiguration("server", "workspace", "project", cloakList, "aworkfolder"))); + assertThat(one, not(new WorkspaceConfiguration("server", "workspace", "project", EMPTY_CLOAKED_PATHS_LIST, "workfolder"))); } } diff --git a/tfs/src/test/java/hudson/plugins/tfs/model/WorkspacesTest.java b/tfs/src/test/java/hudson/plugins/tfs/model/WorkspacesTest.java index b57818e2a..de86227ef 100644 --- a/tfs/src/test/java/hudson/plugins/tfs/model/WorkspacesTest.java +++ b/tfs/src/test/java/hudson/plugins/tfs/model/WorkspacesTest.java @@ -5,8 +5,6 @@ import java.io.StringReader; import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.TreeMap; import hudson.plugins.tfs.commands.ListWorkspacesCommand; @@ -23,7 +21,6 @@ public class WorkspacesTest { private static final List EMPTY_CLOAKED_PATHS_LIST = Collections.emptyList(); - private static final Map EMPTY_MAPPED_PATHS_MAP = new TreeMap(); @Mock private Server server; private ListWorkspacesCommand parser; @@ -94,7 +91,7 @@ public void assertNewWorkspaceIsAddedToMap() throws Exception { when(server.execute(isA(Callable.class))).thenReturn(null); Workspaces workspaces = new Workspaces(server); - Workspace workspace = workspaces.newWorkspace("name1", null, EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, null); + Workspace workspace = workspaces.newWorkspace("name1", null, EMPTY_CLOAKED_PATHS_LIST, null); assertNotNull("The new workspace was null", workspace); assertTrue("The workspace was reported as non existant", workspaces.exists(workspace)); } @@ -104,7 +101,7 @@ public void assertGettingNewWorkspaceIsNotRetrievingServerList() throws Exceptio when(server.execute(isA(Callable.class))).thenReturn(null); Workspaces workspaces = new Workspaces(server); - workspaces.newWorkspace("name1", null, EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, null); + workspaces.newWorkspace("name1", null, EMPTY_CLOAKED_PATHS_LIST, null); assertNotNull("The get new workspace returned null", workspaces.getWorkspace("name1")); verify(server, times(1)).execute(isA(Callable.class)); } @@ -114,7 +111,7 @@ public void assertNewWorkspaceExistsIsNotRetrievingServerList() throws Exception when(server.execute(isA(Callable.class))).thenReturn(null); Workspaces workspaces = new Workspaces(server); - Workspace workspace = workspaces.newWorkspace("name1", null, EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, null); + Workspace workspace = workspaces.newWorkspace("name1", null, EMPTY_CLOAKED_PATHS_LIST, null); assertTrue("The get new workspace did not exists", workspaces.exists(workspace)); verify(server, times(1)).execute(isA(Callable.class)); } @@ -125,7 +122,7 @@ public void assertWorkspaceIsDeletedFromMap() throws Exception { Workspaces workspaces = new Workspaces(server); // Populate the map in test object assertFalse("The workspace was reported as existant", workspaces.exists(new Workspace("name"))); - Workspace workspace = workspaces.newWorkspace("name", null, EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPED_PATHS_MAP, null); + Workspace workspace = workspaces.newWorkspace("name", null, EMPTY_CLOAKED_PATHS_LIST, null); assertTrue("The workspace was reported as non existant", workspaces.exists(new Workspace("name"))); workspaces.deleteWorkspace(workspace); assertFalse("The workspace was reported as existant", workspaces.exists(workspace)); diff --git a/tfs/src/test/java/hudson/plugins/tfs/util/BuildWorkspaceConfigurationRetrieverTest.java b/tfs/src/test/java/hudson/plugins/tfs/util/BuildWorkspaceConfigurationRetrieverTest.java index 7765f8548..02c51f3dc 100644 --- a/tfs/src/test/java/hudson/plugins/tfs/util/BuildWorkspaceConfigurationRetrieverTest.java +++ b/tfs/src/test/java/hudson/plugins/tfs/util/BuildWorkspaceConfigurationRetrieverTest.java @@ -7,8 +7,6 @@ import java.io.IOException; import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.TreeMap; import hudson.model.AbstractBuild; import hudson.model.Node; @@ -23,14 +21,13 @@ public class BuildWorkspaceConfigurationRetrieverTest { private static final List EMPTY_CLOAKED_PATHS_LIST = Collections.emptyList(); - private static final Map EMPTY_MAPPING_PATHS_LIST = new TreeMap(); @Test public void assertGetLatestConfgiurationOnNode() { AbstractBuild build = mock(AbstractBuild.class); Node node = mock(Node.class); Node needleNode = mock(Node.class); - WorkspaceConfiguration configuration = new WorkspaceConfiguration("serverUrl", "workspaceName", "projectPath", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPING_PATHS_LIST, "workfolder"); + WorkspaceConfiguration configuration = new WorkspaceConfiguration("serverUrl", "workspaceName", "projectPath", EMPTY_CLOAKED_PATHS_LIST, "workfolder"); when(build.getPreviousBuild()).thenReturn(build).thenReturn(null); when(build.getBuiltOn()).thenReturn(node, node, null); when(node.getNodeName()).thenReturn("node1", "needleNode"); @@ -101,7 +98,7 @@ public void assertSaveWorkspaceConfigurationUsesSaveOnBuild() throws IOException when(build.getPreviousBuild()).thenReturn(build); when(build.getBuiltOn()).thenReturn(node); when(node.getNodeName()).thenReturn("needleNode"); - when(build.getAction(WorkspaceConfiguration.class)).thenReturn(new WorkspaceConfiguration("serverUrl", "workspaceName", "projectPath", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPING_PATHS_LIST, "workfolder")); + when(build.getAction(WorkspaceConfiguration.class)).thenReturn(new WorkspaceConfiguration("serverUrl", "workspaceName", "projectPath", EMPTY_CLOAKED_PATHS_LIST, "workfolder")); BuildWorkspaceConfiguration configuration = new BuildWorkspaceConfigurationRetriever().getLatestForNode(node, build); assertThat( configuration.getWorkspaceName(), is("workspaceName")); @@ -116,7 +113,7 @@ public void assertGetLatestConfgiurationOnPreviousDeletedNode() { AbstractBuild build = mock(AbstractBuild.class); Node node = mock(Node.class); Node needleNode = mock(Node.class); - WorkspaceConfiguration configuration = new WorkspaceConfiguration("serverUrl", "workspaceName", "projectPath", EMPTY_CLOAKED_PATHS_LIST, EMPTY_MAPPING_PATHS_LIST, "workfolder"); + WorkspaceConfiguration configuration = new WorkspaceConfiguration("serverUrl", "workspaceName", "projectPath", EMPTY_CLOAKED_PATHS_LIST, "workfolder"); when(build.getPreviousBuild()).thenReturn(build).thenReturn(null); when(build.getBuiltOn()).thenReturn(null); From 3de417afb7afc6fa45c3eea3e56e90f4a2a7cbb1 Mon Sep 17 00:00:00 2001 From: kelliejos <31480321+kelliejos@users.noreply.github.com> Date: Fri, 23 Mar 2018 10:10:45 -0400 Subject: [PATCH 06/42] Prepare for release 5.133.0 (#197) --- ReleaseNotes.md | 12 +++++------- pom.xml | 2 +- tfs-sdk/pom.xml | 2 +- tfs/pom.xml | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index bbf21bdc0..85ac2f653 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,11 +1,9 @@ -These notes are for release **5.126.0**. +These notes are for release **5.133.0**. Other releases and their notes can be found at the [tfs-plugin GitHub Releases](https://github.com/jenkinsci/tfs-plugin/releases) page. * Major: - * Update VSTS PR Jenkins build status back to VSTS. Thanks to @smile21prc for pull request #175 - * Use default values for parameters when value not supplied, via pull request #177 - * Add pipeline support. Thanks to @adelcast for pull request #176 + * Update POMs and cleanup the dependencies/code. Thanks to @oleg-nenashev for pull request #192 + * Release configuration and archiving VSTS logs improvement, Thanks to @zackliu for pull request #189 + * Use owner instead of committer as author. Thanks to @adelcast for pull request #188 * Minor: - * Add code to explicitly require either TLS 1.1 or 1.2, via pull request #179 - * Fixing missing import. Thanks to @pvandervelde for pull request #180 - + * Fixing the TFS/VSTS changeset URL [JENKINS-48341](https://issues.jenkins-ci.org/browse/JENKINS-48341), via pull request #190 diff --git a/pom.xml b/pom.xml index baf455480..f81f5cb6f 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ tfs-parent pom Team Foundation Server Plug-in parent - 5.126.0-SNAPSHOT + 5.133.0-SNAPSHOT 1.642.3 diff --git a/tfs-sdk/pom.xml b/tfs-sdk/pom.xml index 96454b627..89884ad1c 100644 --- a/tfs-sdk/pom.xml +++ b/tfs-sdk/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.126.0-SNAPSHOT + 5.133.0-SNAPSHOT tfs-sdk diff --git a/tfs/pom.xml b/tfs/pom.xml index 6cd5af2c0..50c00d0ec 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.126.0-SNAPSHOT + 5.133.0-SNAPSHOT tfs From c4504108f1b3ef8894317c41ea043cea8ada606c Mon Sep 17 00:00:00 2001 From: Jason Prickett Date: Fri, 23 Mar 2018 14:42:20 +0000 Subject: [PATCH 07/42] [maven-release-plugin] prepare release tfs-5.133.0 --- pom.xml | 4 ++-- tfs-sdk/pom.xml | 2 +- tfs/pom.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index f81f5cb6f..f39c10cec 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ tfs-parent pom Team Foundation Server Plug-in parent - 5.133.0-SNAPSHOT + 5.133.0 1.642.3 @@ -57,7 +57,7 @@ scm:git:https://github.com/jenkinsci/tfs-plugin.git scm:git:https://github.com/jenkinsci/tfs-plugin.git - HEAD + tfs-5.133.0 diff --git a/tfs-sdk/pom.xml b/tfs-sdk/pom.xml index 89884ad1c..2b1ac9ced 100644 --- a/tfs-sdk/pom.xml +++ b/tfs-sdk/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.133.0-SNAPSHOT + 5.133.0 tfs-sdk diff --git a/tfs/pom.xml b/tfs/pom.xml index 50c00d0ec..2fd33bdd5 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -3,13 +3,13 @@ org.jenkins-ci.plugins tfs-parent - 5.133.0-SNAPSHOT + 5.133.0 tfs hpi Team Foundation Server Plug-in - 5.126.0-SNAPSHOT + 5.133.0 http://wiki.jenkins-ci.org/display/JENKINS/Team+Foundation+Server+Plugin From 0dcc6f99250d98db6d99f1320029f907d1005048 Mon Sep 17 00:00:00 2001 From: Jason Prickett Date: Fri, 23 Mar 2018 14:42:26 +0000 Subject: [PATCH 08/42] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- tfs-sdk/pom.xml | 2 +- tfs/pom.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index f39c10cec..f81f5cb6f 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ tfs-parent pom Team Foundation Server Plug-in parent - 5.133.0 + 5.133.0-SNAPSHOT 1.642.3 @@ -57,7 +57,7 @@ scm:git:https://github.com/jenkinsci/tfs-plugin.git scm:git:https://github.com/jenkinsci/tfs-plugin.git - tfs-5.133.0 + HEAD diff --git a/tfs-sdk/pom.xml b/tfs-sdk/pom.xml index 2b1ac9ced..89884ad1c 100644 --- a/tfs-sdk/pom.xml +++ b/tfs-sdk/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.133.0 + 5.133.0-SNAPSHOT tfs-sdk diff --git a/tfs/pom.xml b/tfs/pom.xml index 2fd33bdd5..68a684f0d 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -3,13 +3,13 @@ org.jenkins-ci.plugins tfs-parent - 5.133.0 + 5.133.0-SNAPSHOT tfs hpi Team Foundation Server Plug-in - 5.133.0 + 5.133.0-SNAPSHOT http://wiki.jenkins-ci.org/display/JENKINS/Team+Foundation+Server+Plugin From de8c46ab72f750019ffc14b5f3d43f12dc478093 Mon Sep 17 00:00:00 2001 From: Kalyan Date: Tue, 12 Jun 2018 19:28:56 +0530 Subject: [PATCH 09/42] Recreating the instanceReference object correctly for every artifact, otherwise same version is applied for all the available artifacts. (#198) --- .../main/java/hudson/plugins/tfs/rm/ReleaseManagementCI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseManagementCI.java b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseManagementCI.java index 0c865b60f..2e1a10893 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseManagementCI.java +++ b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseManagementCI.java @@ -244,10 +244,10 @@ void CreateRelease( private List PrepareReleaseArtifacts(ReleaseDefinition releaseDefinition, Artifact jenkinsArtifact, String buildNumber, int buildId, BuildListener listener, ReleaseManagementHttpClient releaseManagementHttpClient) throws ReleaseManagementException { List releaseArtifacts = new ArrayList(); - InstanceReference instanceReference = new InstanceReference(); for(final Artifact artifact : releaseDefinition.getArtifacts()) { ReleaseArtifact releaseArtifact = new ReleaseArtifact(); + InstanceReference instanceReference = new InstanceReference(); if(artifact == jenkinsArtifact) { instanceReference.setName(buildNumber); From 05a12336dbd00b021062b04a8cc03bb1ac0035e4 Mon Sep 17 00:00:00 2001 From: Kalyan Date: Wed, 27 Jun 2018 19:41:52 +0530 Subject: [PATCH 10/42] updating authors list (#203) * updating authors list * updated correct timezone --- pom.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pom.xml b/pom.xml index f81f5cb6f..01eaacbf2 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,18 @@ jeyou@microsoft.com -5 + + jenkinssriramb + Sriram Balasubramaniyan + sriramb@microsoft.com + +5 + + + kalyan + Kalyan + kasubram@microsoft.com + +5 + kelliejos Kellie Jos From 5c4d1b119102c1e26d4fa3288329c62689960150 Mon Sep 17 00:00:00 2001 From: Kalyan Date: Tue, 31 Jul 2018 17:01:41 +0530 Subject: [PATCH 11/42] To release 5.133.1 version --- ReleaseNotes.md | 8 ++------ pom.xml | 4 ++-- tfs-sdk/pom.xml | 2 +- tfs/pom.xml | 4 ++-- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 85ac2f653..1700a6bc5 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,9 +1,5 @@ -These notes are for release **5.133.0**. +These notes are for release **5.133.1**. Other releases and their notes can be found at the [tfs-plugin GitHub Releases](https://github.com/jenkinsci/tfs-plugin/releases) page. -* Major: - * Update POMs and cleanup the dependencies/code. Thanks to @oleg-nenashev for pull request #192 - * Release configuration and archiving VSTS logs improvement, Thanks to @zackliu for pull request #189 - * Use owner instead of committer as author. Thanks to @adelcast for pull request #188 * Minor: - * Fixing the TFS/VSTS changeset URL [JENKINS-48341](https://issues.jenkins-ci.org/browse/JENKINS-48341), via pull request #190 + * Fixing release creation post build action when having multiple artifacts in the release definition via pull request #198 diff --git a/pom.xml b/pom.xml index 01eaacbf2..a09888d1f 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ tfs-parent pom Team Foundation Server Plug-in parent - 5.133.0-SNAPSHOT + 5.133.1 1.642.3 @@ -69,7 +69,7 @@ scm:git:https://github.com/jenkinsci/tfs-plugin.git scm:git:https://github.com/jenkinsci/tfs-plugin.git - HEAD + tfs-5.133.1 diff --git a/tfs-sdk/pom.xml b/tfs-sdk/pom.xml index 89884ad1c..2d37a998c 100644 --- a/tfs-sdk/pom.xml +++ b/tfs-sdk/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.133.0-SNAPSHOT + 5.133.1 tfs-sdk diff --git a/tfs/pom.xml b/tfs/pom.xml index 68a684f0d..bbc15809b 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -3,13 +3,13 @@ org.jenkins-ci.plugins tfs-parent - 5.133.0-SNAPSHOT + 5.133.1 tfs hpi Team Foundation Server Plug-in - 5.133.0-SNAPSHOT + 5.133.1 http://wiki.jenkins-ci.org/display/JENKINS/Team+Foundation+Server+Plugin From 0d6adac903ba79117909ed8623fe0705e0a5a0be Mon Sep 17 00:00:00 2001 From: Kalyan Date: Tue, 31 Jul 2018 20:18:29 +0530 Subject: [PATCH 12/42] setting the current version --- pom.xml | 2 +- tfs-sdk/pom.xml | 2 +- tfs/pom.xml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index a09888d1f..051ae08f8 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ tfs-parent pom Team Foundation Server Plug-in parent - 5.133.1 + 5.139.0 1.642.3 diff --git a/tfs-sdk/pom.xml b/tfs-sdk/pom.xml index 2d37a998c..67ed87c9f 100644 --- a/tfs-sdk/pom.xml +++ b/tfs-sdk/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.133.1 + 5.139.0 tfs-sdk diff --git a/tfs/pom.xml b/tfs/pom.xml index bbc15809b..dd53b2546 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -3,13 +3,13 @@ org.jenkins-ci.plugins tfs-parent - 5.133.1 + 5.139.0 tfs hpi Team Foundation Server Plug-in - 5.133.1 + 5.139.0 http://wiki.jenkins-ci.org/display/JENKINS/Team+Foundation+Server+Plugin From 1e29edbc13f6604a7172d7d1a64ddc94b672b7f7 Mon Sep 17 00:00:00 2001 From: Kalyan Date: Tue, 31 Jul 2018 20:19:31 +0530 Subject: [PATCH 13/42] fixing release notes for the correct version --- ReleaseNotes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 1700a6bc5..9e07aeae5 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,4 +1,4 @@ -These notes are for release **5.133.1**. +These notes are for release **5.139.0**. Other releases and their notes can be found at the [tfs-plugin GitHub Releases](https://github.com/jenkinsci/tfs-plugin/releases) page. * Minor: From 46a5299d226384740dafd49e5efc8c6c81d81995 Mon Sep 17 00:00:00 2001 From: Kalyan Date: Tue, 31 Jul 2018 20:20:13 +0530 Subject: [PATCH 14/42] fixing the tag --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 051ae08f8..5090f0432 100644 --- a/pom.xml +++ b/pom.xml @@ -69,7 +69,7 @@ scm:git:https://github.com/jenkinsci/tfs-plugin.git scm:git:https://github.com/jenkinsci/tfs-plugin.git - tfs-5.133.1 + tfs-5.139.0 From a548eec8b87804507542e2cfe9432e7e66e7c8f0 Mon Sep 17 00:00:00 2001 From: Kalyan Date: Wed, 1 Aug 2018 00:03:13 +0530 Subject: [PATCH 15/42] bumping version to 5.139.1 (#206) --- ReleaseNotes.md | 2 +- pom.xml | 4 ++-- tfs-sdk/pom.xml | 2 +- tfs/pom.xml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 9e07aeae5..bdc102b91 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,4 +1,4 @@ -These notes are for release **5.139.0**. +These notes are for release **5.139.1**. Other releases and their notes can be found at the [tfs-plugin GitHub Releases](https://github.com/jenkinsci/tfs-plugin/releases) page. * Minor: diff --git a/pom.xml b/pom.xml index 5090f0432..397809e12 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ tfs-parent pom Team Foundation Server Plug-in parent - 5.139.0 + 5.139.1 1.642.3 @@ -69,7 +69,7 @@ scm:git:https://github.com/jenkinsci/tfs-plugin.git scm:git:https://github.com/jenkinsci/tfs-plugin.git - tfs-5.139.0 + tfs-5.139.1 diff --git a/tfs-sdk/pom.xml b/tfs-sdk/pom.xml index 67ed87c9f..a7308ca33 100644 --- a/tfs-sdk/pom.xml +++ b/tfs-sdk/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.139.0 + 5.139.1 tfs-sdk diff --git a/tfs/pom.xml b/tfs/pom.xml index dd53b2546..93f859230 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -3,13 +3,13 @@ org.jenkins-ci.plugins tfs-parent - 5.139.0 + 5.139.1 tfs hpi Team Foundation Server Plug-in - 5.139.0 + 5.139.1 http://wiki.jenkins-ci.org/display/JENKINS/Team+Foundation+Server+Plugin From 90044ee68fc9e468375d9cc4fadd72f513489daa Mon Sep 17 00:00:00 2001 From: Kalyan Date: Wed, 1 Aug 2018 23:12:43 +0530 Subject: [PATCH 16/42] bumping version to 5.139.2. Added missing SNAPSHOT keyword in version (#207) * bumping version to 5.139.2. Added missing SNAPSHOT keyword in version * minor fix --- ReleaseNotes.md | 4 ++-- pom.xml | 4 ++-- tfs-sdk/pom.xml | 2 +- tfs/pom.xml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index bdc102b91..cdc13536f 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,5 +1,5 @@ -These notes are for release **5.139.1**. +These notes are for release **5.139.2**. Other releases and their notes can be found at the [tfs-plugin GitHub Releases](https://github.com/jenkinsci/tfs-plugin/releases) page. * Minor: - * Fixing release creation post build action when having multiple artifacts in the release definition via pull request #198 + * Fixing release creation post build action when having multiple artifacts in the release definition, via pull request #198 diff --git a/pom.xml b/pom.xml index 397809e12..662926e8a 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ tfs-parent pom Team Foundation Server Plug-in parent - 5.139.1 + 5.139.2-SNAPSHOT 1.642.3 @@ -69,7 +69,7 @@ scm:git:https://github.com/jenkinsci/tfs-plugin.git scm:git:https://github.com/jenkinsci/tfs-plugin.git - tfs-5.139.1 + HEAD diff --git a/tfs-sdk/pom.xml b/tfs-sdk/pom.xml index a7308ca33..8a8d09ef3 100644 --- a/tfs-sdk/pom.xml +++ b/tfs-sdk/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.139.1 + 5.139.2-SNAPSHOT tfs-sdk diff --git a/tfs/pom.xml b/tfs/pom.xml index 93f859230..d5d63072b 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -3,13 +3,13 @@ org.jenkins-ci.plugins tfs-parent - 5.139.1 + 5.139.2-SNAPSHOT tfs hpi Team Foundation Server Plug-in - 5.139.1 + 5.139.2-SNAPSHOT http://wiki.jenkins-ci.org/display/JENKINS/Team+Foundation+Server+Plugin From 867936c1de79800ae4448a01d2d30509f592c21d Mon Sep 17 00:00:00 2001 From: Jason Prickett Date: Wed, 1 Aug 2018 17:55:30 +0000 Subject: [PATCH 17/42] [maven-release-plugin] prepare release tfs-5.139.2 --- pom.xml | 4 ++-- tfs-sdk/pom.xml | 2 +- tfs/pom.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 662926e8a..807d83b5e 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ tfs-parent pom Team Foundation Server Plug-in parent - 5.139.2-SNAPSHOT + 5.139.2 1.642.3 @@ -69,7 +69,7 @@ scm:git:https://github.com/jenkinsci/tfs-plugin.git scm:git:https://github.com/jenkinsci/tfs-plugin.git - HEAD + tfs-5.139.2 diff --git a/tfs-sdk/pom.xml b/tfs-sdk/pom.xml index 8a8d09ef3..c8a0f5605 100644 --- a/tfs-sdk/pom.xml +++ b/tfs-sdk/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.139.2-SNAPSHOT + 5.139.2 tfs-sdk diff --git a/tfs/pom.xml b/tfs/pom.xml index d5d63072b..0ed10050c 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -3,13 +3,13 @@ org.jenkins-ci.plugins tfs-parent - 5.139.2-SNAPSHOT + 5.139.2 tfs hpi Team Foundation Server Plug-in - 5.139.2-SNAPSHOT + 5.139.2 http://wiki.jenkins-ci.org/display/JENKINS/Team+Foundation+Server+Plugin From 2070fb0b7bdc820638216c23bd4ff46a3aeb9212 Mon Sep 17 00:00:00 2001 From: Jason Prickett Date: Wed, 1 Aug 2018 17:55:37 +0000 Subject: [PATCH 18/42] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- tfs-sdk/pom.xml | 2 +- tfs/pom.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 807d83b5e..662926e8a 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ tfs-parent pom Team Foundation Server Plug-in parent - 5.139.2 + 5.139.2-SNAPSHOT 1.642.3 @@ -69,7 +69,7 @@ scm:git:https://github.com/jenkinsci/tfs-plugin.git scm:git:https://github.com/jenkinsci/tfs-plugin.git - tfs-5.139.2 + HEAD diff --git a/tfs-sdk/pom.xml b/tfs-sdk/pom.xml index c8a0f5605..8a8d09ef3 100644 --- a/tfs-sdk/pom.xml +++ b/tfs-sdk/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.139.2 + 5.139.2-SNAPSHOT tfs-sdk diff --git a/tfs/pom.xml b/tfs/pom.xml index 0ed10050c..d5d63072b 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -3,13 +3,13 @@ org.jenkins-ci.plugins tfs-parent - 5.139.2 + 5.139.2-SNAPSHOT tfs hpi Team Foundation Server Plug-in - 5.139.2 + 5.139.2-SNAPSHOT http://wiki.jenkins-ci.org/display/JENKINS/Team+Foundation+Server+Plugin From 02c5a77246445b2484a45c73b8e0c427b3751c34 Mon Sep 17 00:00:00 2001 From: kelliejos <31480321+kelliejos@users.noreply.github.com> Date: Wed, 1 Aug 2018 14:30:21 -0400 Subject: [PATCH 19/42] Clear out Release Notes for next release. --- ReleaseNotes.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index cdc13536f..90f7018d0 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,5 +1,8 @@ -These notes are for release **5.139.2**. +These notes are for release **(to be determined)**. Other releases and their notes can be found at the [tfs-plugin GitHub Releases](https://github.com/jenkinsci/tfs-plugin/releases) page. +* Major + * TODO + * Minor: - * Fixing release creation post build action when having multiple artifacts in the release definition, via pull request #198 + * TODO From fd9e94476d92af531bfacabde61ff4633651d3d2 Mon Sep 17 00:00:00 2001 From: Kalyan Date: Mon, 20 Aug 2018 01:22:05 -0700 Subject: [PATCH 20/42] RM webhook changes (#201) * RM WebHook changes. --- .../tfs/rm/ConnectReleaseWebHookEvent.java | 327 ++++++++++++++++++ .../hudson/plugins/tfs/rm/ReleaseWebHook.java | 112 ++++++ .../plugins/tfs/rm/ReleaseWebHookAction.java | 160 +++++++++ .../plugins/tfs/rm/ReleaseWebHookHelper.java | 53 +++ .../tfs/rm/ReleaseWebHookReference.java | 60 ++++ .../tfs/rm/ReleaseWebHookResource.java | 53 +++ .../plugins/tfs/rm/ReleaseWebHookStatus.java | 43 +++ .../tfs/rm/ReleaseWebHookSummaryAction.java | 21 ++ .../tfs/rm/ReleaseWebHook/config.groovy | 21 ++ .../rm/ReleaseWebHook/help-payloadUrl.html | 3 + .../tfs/rm/ReleaseWebHook/help-secret.html | 3 + .../rm/ReleaseWebHook/help-webhookName.html | 3 + .../tfs/rm/ReleaseWebHookAction/config.groovy | 9 + .../rm/ReleaseWebHookReference/config.groovy | 13 + .../help-webHookName.html | 3 + .../ReleaseWebHookSummaryAction/summary.jelly | 30 ++ 16 files changed, 914 insertions(+) create mode 100644 tfs/src/main/java/hudson/plugins/tfs/rm/ConnectReleaseWebHookEvent.java create mode 100644 tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHook.java create mode 100644 tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookAction.java create mode 100644 tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookHelper.java create mode 100644 tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookReference.java create mode 100644 tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookResource.java create mode 100644 tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookStatus.java create mode 100644 tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookSummaryAction.java create mode 100644 tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHook/config.groovy create mode 100644 tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHook/help-payloadUrl.html create mode 100644 tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHook/help-secret.html create mode 100644 tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHook/help-webhookName.html create mode 100644 tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHookAction/config.groovy create mode 100644 tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHookReference/config.groovy create mode 100644 tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHookReference/help-webHookName.html create mode 100644 tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHookSummaryAction/summary.jelly diff --git a/tfs/src/main/java/hudson/plugins/tfs/rm/ConnectReleaseWebHookEvent.java b/tfs/src/main/java/hudson/plugins/tfs/rm/ConnectReleaseWebHookEvent.java new file mode 100644 index 000000000..04875fcec --- /dev/null +++ b/tfs/src/main/java/hudson/plugins/tfs/rm/ConnectReleaseWebHookEvent.java @@ -0,0 +1,327 @@ +package hudson.plugins.tfs.rm; + +import com.fasterxml.jackson.databind.ObjectMapper; +import hudson.model.AbstractProject; +import hudson.model.Descriptor; +import hudson.model.Item; +import hudson.plugins.tfs.TeamPluginGlobalConfig; +import hudson.plugins.tfs.model.AbstractHookEvent; +import hudson.plugins.tfs.model.servicehooks.Event; +import hudson.tasks.Publisher; +import hudson.util.DescribableList; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.security.InvalidParameterException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; +import jenkins.model.Jenkins; +import net.sf.json.JSONArray; +import net.sf.json.JSONObject; +import org.apache.commons.lang.StringUtils; + +/** + * This HookEvent is for to create/delete Release Webhook. +*/ +public class ConnectReleaseWebHookEvent extends AbstractHookEvent { + + private static final Logger logger = Logger.getLogger(ReleaseWebHookAction.class.getName()); + private static final String CREATE_WEBHOOK_EVENTNAME = "rmwebhook-create"; + private static final String REMOVE_WEBHOOK_ENENTNAME = "rmwebhook-remove"; + private static final String LIST_WEBHOOK_EVENTNAME = "rmwebhook-list"; + private static final String LINK_WEBHOOK_EVENTNAME = "rmwebhook-link"; + private static final String UNLINK_WEBHOOK_EVENTNAME = "rmwebhook-unlink"; + + /** + * Factory to create ConnectReleaseWebHookEvent. + */ + public static class Factory implements AbstractHookEvent.Factory { + @Override + public ConnectReleaseWebHookEvent create() { + return new ConnectReleaseWebHookEvent(); + } + + @Override + public String getSampleRequestPayload() { + return "{\n" + + " \"eventType\": rmwebhook-create\n" + + " \"resource\": {" + + " \"webhookName\": \"webhook name\"\n" + + " \"payloadUrl\": \"https://xplatalm.visualstudio.com/_apis/Release/receiveExternalEvent/wenhookId\"\n" + + " \"secret\": \"secret\"\n" + + " }" + + "}"; + } + } + + @Override + public JSONObject perform(final ObjectMapper mapper, final Event event, final String message, final String detailedMessage) { + final Object resource = event.getResource(); + final ReleaseWebHookResource parameters = mapper.convertValue(resource, ReleaseWebHookResource.class); + + if (event.getEventType().equalsIgnoreCase(CREATE_WEBHOOK_EVENTNAME)) { + createReleaseWebHook(parameters); + } else if (event.getEventType().equalsIgnoreCase(REMOVE_WEBHOOK_ENENTNAME)) { + deleteReleaseWebHook(parameters); + } else if (event.getEventType().equalsIgnoreCase(LIST_WEBHOOK_EVENTNAME)) { + return listReleaseWebHook(); + } else if (event.getEventType().equalsIgnoreCase(LINK_WEBHOOK_EVENTNAME)) { + linkWebHook(parameters); + } else if (event.getEventType().equalsIgnoreCase(UNLINK_WEBHOOK_EVENTNAME)) { + unlinkWebHook(parameters); + } else { + throw new UnsupportedOperationException("Webhook operation " + parameters.getOperationType() + " is not supported"); + } + + return JSONObject.fromObject(event); + } + + private String validateAndGetPayloadUrl(final ReleaseWebHookResource parameters) { + String payloadUrl = parameters.getPayloadUrl(); + + if (StringUtils.isBlank(payloadUrl)) { + throw new InvalidParameterException("PayloadUrl is empty"); + } + + final URI uri; + try { + uri = new URI(payloadUrl); + } catch (final URISyntaxException e) { + throw new InvalidParameterException("Malformed Payload URL " + e.getMessage()); + } + + final String hostName = uri.getHost(); + if (StringUtils.isBlank(hostName)) { + throw new InvalidParameterException("Payload URL is empty"); + } + + return StringUtils.stripEnd(StringUtils.trim(payloadUrl), "/"); + } + + private AbstractProject validateAndGetJenkinsProject(final ReleaseWebHookResource resource) { + String projectName = resource.getProjectName(); + + if (StringUtils.isBlank(projectName)) { + throw new InvalidParameterException("Project name is empty"); + } + + for (final Item project : Jenkins.getActiveInstance().getAllItems()) { + if (project instanceof AbstractProject && project.getName().equalsIgnoreCase(projectName)) { + return (AbstractProject) project; + } + } + + throw new InvalidParameterException("Cannot find Jenkins Project with the name " + resource.getProjectName()); + } + + private ReleaseWebHook validateAndGetReleaseWebHookByName(final String webHookName) { + ReleaseWebHook webHook = null; + for (ReleaseWebHook webHookItem : ReleaseWebHookHelper.getReleaseWebHookConfigurations()) { + if (webHookItem.getWebHookName().equalsIgnoreCase(webHookName)) { + webHook = webHookItem; + logger.fine(String.format("WebHook found for webhook name %s", webHookName)); + break; + } + } + + if (webHook == null) { + throw new InvalidParameterException(String.format("Cannot find webhook with the name %s", webHookName)); + } + + return webHook; + } + + private void createReleaseWebHook(final ReleaseWebHookResource resource) { + if (resource == null) { + throw new InvalidParameterException("resource is null"); + } + + String payloadUrl = validateAndGetPayloadUrl(resource); + String secret = resource.getSecret(); + String webHookName = resource.getWebHookName(); + + if (StringUtils.isBlank(webHookName)) { + throw new InvalidParameterException("webhook name is empty"); + } + + List releaseWebHooks = ReleaseWebHookHelper.getReleaseWebHookConfigurations(); + for (ReleaseWebHook webHook : releaseWebHooks) { + if (webHook.getWebHookName().equalsIgnoreCase(webHookName)) { + if (webHook.getPayloadUrl().equalsIgnoreCase(payloadUrl)) { + logger.fine(String.format("WebHook with the name %s and payloadUrl %s already exists", webHookName, payloadUrl)); + return; + } else { + throw new InvalidParameterException(String.format("WebHook with the name %s already exists. Provide a unique name for the ReleaseWebHook", webHookName)); + } + } + } + + ReleaseWebHook webHook = new ReleaseWebHook(webHookName, payloadUrl, secret); + releaseWebHooks.add(webHook); + ReleaseWebHookHelper.saveReleaseWebHookConfigurations(releaseWebHooks); + } + + private void deleteReleaseWebHook(final ReleaseWebHookResource resource) { + if (resource == null) { + throw new InvalidParameterException("resource is null"); + } + + final TeamPluginGlobalConfig config = TeamPluginGlobalConfig.get(); + if (config == null) { + throw new InternalError("Cannot load TFS global configuration"); + } + + String webHookNameToDelete = resource.getWebHookName(); + ReleaseWebHook webHook = validateAndGetReleaseWebHookByName(webHookNameToDelete); + + for (final Item projectItem : Jenkins.getActiveInstance().getAllItems()) { + if (projectItem instanceof AbstractProject) { + AbstractProject project = (AbstractProject) projectItem; + + ReleaseWebHookAction action = getReleaseWebHookActionFromProject(project); + if (action != null) { + for (ReleaseWebHookReference webHookReference : action.getWebHookReferences()) { + if (webHookReference.getWebHookName().equalsIgnoreCase(webHookNameToDelete)) { + throw new UnsupportedOperationException(String.format("WebHook %s is referenced in project %s. Cannot delete until all the references are removed.", webHookNameToDelete, project.getName())); + } + } + } + } + } + + List releaseWebHooks = ReleaseWebHookHelper.getReleaseWebHookConfigurations(); + releaseWebHooks.remove(webHook); + ReleaseWebHookHelper.saveReleaseWebHookConfigurations(releaseWebHooks); + } + + private JSONObject listReleaseWebHook() { + JSONArray webHooks = new JSONArray(); + + for (ReleaseWebHook webHookConfig : ReleaseWebHookHelper.getReleaseWebHookConfigurations()) { + JSONObject webHook = new JSONObject(); + webHook.put("WebHookName", webHookConfig.getWebHookName()); + webHook.put("PayloadUrl", webHookConfig.getPayloadUrl()); + + webHooks.add(webHook); + } + + JSONObject result = new JSONObject(); + result.put("ReleaseWebHooks", webHooks); + + return result; + } + + private void linkWebHook(final ReleaseWebHookResource resource) { + if (resource == null) { + throw new InvalidParameterException("resource is null"); + } + + AbstractProject project = validateAndGetJenkinsProject(resource); + String webHookNameToLink = resource.getWebHookName(); + + // validate the webhook exists in the server config + ReleaseWebHook webHook = validateAndGetReleaseWebHookByName(webHookNameToLink); + + ReleaseWebHookAction action = getReleaseWebHookActionFromProject(project); + + if (action != null && action.getWebHookReferences() != null) { + for (ReleaseWebHookReference webHookReference : action.getWebHookReferences()) { + if (webHookReference.getWebHookName().equalsIgnoreCase(webHookNameToLink)) { + logger.fine(String.format("WebHook %s already added in the project %s", webHookNameToLink, project.getName())); + return; + } + } + } + + ReleaseWebHookReference webHookReference = new ReleaseWebHookReference(webHookNameToLink); + + if (action == null) { + List webHookReferences = new ArrayList(); + webHookReferences.add(webHookReference); + + action = new ReleaseWebHookAction(webHookReferences); + DescribableList> publishersList = project.getPublishersList(); + publishersList.add(action); + } else { + List webHookReferences = action.getWebHookReferences(); + if (webHookReferences == null) { + webHookReferences = new ArrayList(); + } + + webHookReferences.add(webHookReference); + action.setWebHookReferences(webHookReferences); + + logger.fine(String.format("WebHook %s is linked to project %s", webHookReference, project)); + } + + saveProject(project); + } + + private void unlinkWebHook(final ReleaseWebHookResource resource) { + if (resource == null) { + throw new InvalidParameterException("resource is null"); + } + + AbstractProject project = validateAndGetJenkinsProject(resource); + String webHookNameToUnlink = resource.getWebHookName(); + + if (StringUtils.isBlank(webHookNameToUnlink)) { + throw new InvalidParameterException("webhook name is empty"); + } + + ReleaseWebHookAction action = getReleaseWebHookActionFromProject(project); + + if (action == null) { + logger.fine(String.format("Cannot find ReleaseWebHook post build action")); + return; + } + + List webHookReferences = action.getWebHookReferences(); + for (ReleaseWebHookReference webHookReference : webHookReferences) { + if (webHookReference.getWebHookName().equalsIgnoreCase(webHookNameToUnlink)) { + logger.fine(String.format("Found webhook %s in project %s. Removing it", webHookNameToUnlink, project.getName())); + + webHookReferences.remove(webHookReference); + if (webHookReferences.isEmpty()) { + // no webhook exists, removing the post build action itself + logger.fine(String.format("ReleaseWebHook post build action is empty, removing the post build action from project")); + DescribableList> publishersList = project.getPublishersList(); + publishersList.remove(action); + } + + saveProject(project); + return; + } + } + + logger.fine(String.format("Cannnot find webhook with the name %s in project %s", webHookNameToUnlink, project.getName())); + } + + private ReleaseWebHookAction getReleaseWebHookActionFromProject(final AbstractProject project) { + if (project == null) { + return null; + } + + ReleaseWebHookAction action = null; + DescribableList> publishersList = project.getPublishersList(); + + for (Publisher publisher : publishersList) { + if (publisher instanceof ReleaseWebHookAction) { + action = (ReleaseWebHookAction) publisher; + } + } + + return action; + } + + private void saveProject(final AbstractProject project) { + if (project != null) { + try { + project.save(); + } catch (IOException ex) { + throw new InternalError(String.format("Updating project %s failed with an error %s", project.getName(), ex.getMessage())); + } + } + } +} diff --git a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHook.java b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHook.java new file mode 100644 index 000000000..1a3af56de --- /dev/null +++ b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHook.java @@ -0,0 +1,112 @@ +package hudson.plugins.tfs.rm; + +import hudson.Extension; +import hudson.model.AbstractDescribableImpl; +import hudson.model.Descriptor; +import hudson.util.FormValidation; +import java.net.URI; +import java.net.URISyntaxException; +import org.kohsuke.stapler.DataBoundConstructor; + +import java.util.logging.Logger; +import org.apache.commons.lang.StringUtils; +import org.kohsuke.stapler.QueryParameter; + +/** + * + * @author Kalyan + */ +public class ReleaseWebHook extends AbstractDescribableImpl { + private static final Logger logger = Logger.getLogger(ReleaseWebHook.class.getName()); + + private final String webHookName; + private final String payloadUrl; + private final String secret; + + @DataBoundConstructor + public ReleaseWebHook(final String webHookName, final String payloadUrl, final String secret) { + this.webHookName = webHookName; + this.payloadUrl = payloadUrl; + this.secret = secret; + } + + public ReleaseWebHook(final String webHookName, final String payloadUrl) { + this.webHookName = webHookName; + this.payloadUrl = payloadUrl; + this.secret = StringUtils.EMPTY; + } + + public String getWebHookName() { + return this.webHookName; + } + + public String getPayloadUrl() { + return this.payloadUrl; + } + + public String getSecret() { + return this.secret; + } + + @Override + public DescriptorImpl getDescriptor() { + return (DescriptorImpl) super.getDescriptor(); + } + + /** + * DescriptorImpl. + */ + @Extension + public static class DescriptorImpl extends Descriptor { + + @Override + public String getDisplayName() { + return "Release Webhook"; + } + + /** + * Validates Payload URL. + * @param value + * @return + */ + @SuppressWarnings("unused") + public FormValidation doCheckPayloadUrl(@QueryParameter final String value) { + if (StringUtils.isBlank(value)) { + return FormValidation.warning("Please provide a value"); + } + + final URI uri; + try { + uri = new URI(value); + } catch (final URISyntaxException e) { + return FormValidation.error("Malformed Payload URL (%s)", e.getMessage()); + } + + final String hostName = uri.getHost(); + if (StringUtils.isBlank(hostName)) { + return FormValidation.error("Please provide a host name"); + } + + return FormValidation.ok(); + } + + /** + * Validates WebHook Name. + * @param value + * @return + */ + @SuppressWarnings("unused") + public FormValidation doCheckWebHookName(@QueryParameter final String value) { + if (StringUtils.isBlank(value)) { + return FormValidation.error("Please provide a value"); + } + + String pattern = "^[A-Za-z0-9_-]+$"; + if (!value.matches(pattern)) { + return FormValidation.error("Only allowed characters are alphaphetic, numeric, hypen and underscore"); + } + + return FormValidation.ok(); + } + } +} diff --git a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookAction.java b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookAction.java new file mode 100644 index 000000000..64b74931a --- /dev/null +++ b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookAction.java @@ -0,0 +1,160 @@ +package hudson.plugins.tfs.rm; + +import hudson.Extension; +import hudson.Launcher; +import hudson.model.AbstractBuild; +import hudson.model.AbstractProject; +import hudson.model.BuildListener; +import hudson.model.Cause; +import hudson.plugins.tfs.JenkinsEventNotifier; +import hudson.tasks.BuildStepDescriptor; +import hudson.tasks.BuildStepMonitor; +import hudson.tasks.Notifier; +import hudson.tasks.Publisher; +import java.io.IOException; +import java.io.Serializable; +import java.net.HttpURLConnection; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import net.sf.json.JSONObject; +import org.apache.commons.lang.StringUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; +import org.kohsuke.stapler.DataBoundConstructor; + +/** + * Implements ReleaseWebhook Post build action. + * @author Kalyan + */ +public class ReleaseWebHookAction extends Notifier implements Serializable { + + private static final Logger logger = Logger.getLogger(ReleaseWebHookAction.class.getName()); + private List webHookReferences; + private final String apiVersion = "5.0-preview"; + + @DataBoundConstructor + public ReleaseWebHookAction(final List webHookReferences) { + this.webHookReferences = webHookReferences; + } + + @Override + public BuildStepMonitor getRequiredMonitorService() { + return BuildStepMonitor.NONE; + } + + public List getWebHookReferences() { + return this.webHookReferences; + } + + public void setWebHookReferences(final List webHookReferences) { + this.webHookReferences = webHookReferences; + } + + @Override + public boolean perform(final AbstractBuild build, final Launcher launcher, final BuildListener listener) throws InterruptedException, IOException { + logger.entering("ReleaseWebhookAction", "Perform"); + + JSONObject json = new JSONObject(); + final String payload = JenkinsEventNotifier.getApiJson(build.getUrl()); + if (payload != null) { + json = JSONObject.fromObject(payload); + } + + json.put("name", build.getProject().getName()); + json.put("id", build.getNumber()); + json.put("startedBy", getStartedBy(build)); + + HashMap nameToWebHookMap = new HashMap(); + for (ReleaseWebHook webHook : ReleaseWebHookHelper.getReleaseWebHookConfigurations()) { + nameToWebHookMap.put(webHook.getWebHookName(), webHook); + } + + List webHookStatus = new ArrayList(); + ReleaseWebHook webHook = null; + for (ReleaseWebHookReference webHookName : webHookReferences) { + if (nameToWebHookMap.containsKey(webHookName.getWebHookName())) { + try { + webHook = nameToWebHookMap.get(webHookName.getWebHookName()); + logger.fine(String.format("Sending payload event to %s", webHook.getPayloadUrl())); + ReleaseWebHookStatus status = sendJobCompletedEvent(json, webHook); + + webHookStatus.add(status); + } catch (Exception ex) { + logger.log(Level.SEVERE, StringUtils.EMPTY, ex); + webHookStatus.add(new ReleaseWebHookStatus(webHook.getPayloadUrl(), HttpURLConnection.HTTP_INTERNAL_ERROR, ex.toString())); + } + } + } + + build.addAction(new ReleaseWebHookSummaryAction((webHookStatus))); + return true; + } + + private ReleaseWebHookStatus sendJobCompletedEvent(final JSONObject json, final ReleaseWebHook webHook) throws IOException, NoSuchAlgorithmException, InvalidKeyException { + HttpClient client = HttpClientBuilder.create().build(); + final HttpPost request = new HttpPost(webHook.getPayloadUrl()); + final String payload = json.toString(); + + request.addHeader("Content-Type", "application/json"); + request.addHeader("Accept", "application/json; api-version=" + apiVersion); + + if (!StringUtils.isBlank(webHook.getSecret())) { + String signature = ReleaseWebHookHelper.getPayloadSignature(webHook.getSecret(), payload); + request.addHeader("X-Jenkins-Signature", signature); + } + + request.setEntity(new StringEntity(payload)); + final HttpResponse response = client.execute(request); + final int statusCode = response.getStatusLine().getStatusCode(); + + ReleaseWebHookStatus status = null; + if (statusCode == HttpURLConnection.HTTP_OK) { + logger.log(Level.INFO, "sent event payload successfully"); + status = new ReleaseWebHookStatus(webHook.getPayloadUrl(), statusCode); + } else { + HttpEntity entity = response.getEntity(); + String content = EntityUtils.toString(entity); + logger.log(Level.WARNING, "Cannot send the event to webhook. Content:" + content); + status = new ReleaseWebHookStatus(webHook.getPayloadUrl(), statusCode, content); + } + + return status; + } + + /** + * Implementation of DescriptorImpl. + */ + @Extension + public static final class DescriptorImpl extends BuildStepDescriptor { + + @Override + public boolean isApplicable(final Class jobType) { + return true; + } + + @Override + public String getDisplayName() { + return "TFS/Team Services Release Webhook"; + } + } + + private String getStartedBy(final AbstractBuild build) { + final Cause.UserIdCause cause = (Cause.UserIdCause) build.getCause(Cause.UserIdCause.class); + String startedBy = ""; + if (cause != null && cause.getUserId() != null) { + startedBy = cause.getUserId(); + } + + return startedBy; + } +} diff --git a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookHelper.java b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookHelper.java new file mode 100644 index 000000000..2897a39f9 --- /dev/null +++ b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookHelper.java @@ -0,0 +1,53 @@ +package hudson.plugins.tfs.rm; + +import hudson.plugins.tfs.TeamPluginGlobalConfig; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Kalyan + */ +public final class ReleaseWebHookHelper { + private ReleaseWebHookHelper() { + } + + /** + * Gets the release webHook configuration from global config. + * @return list of release webHook + */ + public static List getReleaseWebHookConfigurations() { + final TeamPluginGlobalConfig config = TeamPluginGlobalConfig.get(); + if (config == null) { + throw new InternalError("Cannot load TFS global configuration"); + } + + //return config.getReleaseWebHookConfigurations(); + return new ArrayList(); + } + + /** + * Saves the release webHooks with the global config. + * @param releaseWebHooks + */ + public static void saveReleaseWebHookConfigurations(final List releaseWebHooks) { + final TeamPluginGlobalConfig config = TeamPluginGlobalConfig.get(); + if (config == null) { + throw new InternalError("Cannot load TFS global configuration"); + } + + //config.setReleaseWebHookConfigurations(releaseWebHooks); + config.save(); + } + + /** + * Gets the payload signature for the given event payload. + * @param secret + * @param payload + * @return + */ + public static String getPayloadSignature(final String secret, final String payload) { + //JenkinsEventNotifier.getPayloadSignature(secret payload); + return null; + } +} diff --git a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookReference.java b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookReference.java new file mode 100644 index 000000000..02eb59f78 --- /dev/null +++ b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookReference.java @@ -0,0 +1,60 @@ +package hudson.plugins.tfs.rm; + +import com.cloudbees.plugins.credentials.common.StandardListBoxModel; +import hudson.Extension; +import hudson.model.AbstractDescribableImpl; +import hudson.model.Descriptor; +import hudson.util.ListBoxModel; +import org.kohsuke.stapler.DataBoundConstructor; + +import java.util.logging.Logger; + +/** + * + * @author Kalyan + */ +public class ReleaseWebHookReference extends AbstractDescribableImpl { + private static final Logger logger = Logger.getLogger(ReleaseWebHookReference.class.getName()); + + private final String webHookName; + + @DataBoundConstructor + public ReleaseWebHookReference(final String webHookName) { + this.webHookName = webHookName; + } + + public String getWebHookName() { + return this.webHookName; + } + + @Override + public DescriptorImpl getDescriptor() { + return (DescriptorImpl) super.getDescriptor(); + } + + /** + * DescriptorImpl. + */ + @Extension + public static class DescriptorImpl extends Descriptor { + + @Override + public String getDisplayName() { + return "Release Webhook"; + } + + /** + * Fills the webHook name input. + * @return + */ + public ListBoxModel doFillWebHookNameItems() { + StandardListBoxModel listBoxModel = new StandardListBoxModel(); + + for (ReleaseWebHook webHook : ReleaseWebHookHelper.getReleaseWebHookConfigurations()) { + listBoxModel.add(webHook.getWebHookName()); + } + + return listBoxModel; + } + } +} diff --git a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookResource.java b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookResource.java new file mode 100644 index 000000000..315fdb79e --- /dev/null +++ b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookResource.java @@ -0,0 +1,53 @@ +package hudson.plugins.tfs.rm; + +/** + * Implements ReleaseWebHookResource. Model to represent webHook register event. + * @author Kalyan + */ +public class ReleaseWebHookResource { + private String operationType; + private String webHookName; + private String projectName; + private String payloadUrl; + private String secret; + + public String getOperationType() { + return this.operationType; + } + + public void setOperationType(final String operationType) { + this.operationType = operationType; + } + + public String getWebHookName() { + return this.webHookName; + } + + public void setWebHookName(final String webHookName) { + this.webHookName = webHookName; + } + + public String getProjectName() { + return this.projectName; + } + + public void setProjectName(final String jobName) { + this.projectName = jobName; + } + + public String getPayloadUrl() { + return this.payloadUrl; + } + + public void setPayloadUrl(final String payloadUrl) { + this.payloadUrl = payloadUrl; + } + + public String getSecret() { + return this.secret; + } + + public void setSecret(final String secret) { + this.secret = secret; + } +} diff --git a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookStatus.java b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookStatus.java new file mode 100644 index 000000000..442b462f7 --- /dev/null +++ b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookStatus.java @@ -0,0 +1,43 @@ +package hudson.plugins.tfs.rm; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Implements ReleaseWebHookStatus. + * @author Kalyan + */ +public class ReleaseWebHookStatus { + + private final String payloadUrl; + private final String timeStamp; + private final int statusCode; + private String errorMessage; + + public ReleaseWebHookStatus(final String payloadUrl, final int status) { + this.payloadUrl = payloadUrl; + this.statusCode = status; + this.timeStamp = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(new Date()); + } + + public ReleaseWebHookStatus(final String payloadUrl, final int status, final String errorMessage) { + this(payloadUrl, status); + this.errorMessage = errorMessage; + } + + public String getPayloadUrl() { + return this.payloadUrl; + } + + public int getStatusCode() { + return this.statusCode; + } + + public String getErrorMessage() { + return this.errorMessage; + } + + public String getTimeStamp() { + return this.timeStamp; + } +} diff --git a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookSummaryAction.java b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookSummaryAction.java new file mode 100644 index 000000000..d8984ec54 --- /dev/null +++ b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookSummaryAction.java @@ -0,0 +1,21 @@ +package hudson.plugins.tfs.rm; + +import hudson.model.InvisibleAction; +import java.util.List; + +/** + * Implements ReleaseWebhook Post build action. + * @author Kalyan + */ +public class ReleaseWebHookSummaryAction extends InvisibleAction { + + private List webHookStatus; + + public ReleaseWebHookSummaryAction(final List webHookStatus) { + this.webHookStatus = webHookStatus; + } + + public List getWebHookStatus() { + return this.webHookStatus; + } +} diff --git a/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHook/config.groovy b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHook/config.groovy new file mode 100644 index 000000000..8bf84fd52 --- /dev/null +++ b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHook/config.groovy @@ -0,0 +1,21 @@ +package hudson.plugins.tfs.rm.ReleaseWebHook; + +def f = namespace(lib.FormTagLib); + +f.entry(title: _("WebHook Name"), field: "webHookName") { + f.textbox(); +} + +f.entry(title: _("Payload URL"), field: "payloadUrl") { + f.textbox(); +} + +f.entry(title: _("Secret"), field: "secret") { + f.password(); +} + +f.entry { + div(align: "right") { + f.repeatableDeleteButton() + } +} \ No newline at end of file diff --git a/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHook/help-payloadUrl.html b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHook/help-payloadUrl.html new file mode 100644 index 000000000..ea49375b8 --- /dev/null +++ b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHook/help-payloadUrl.html @@ -0,0 +1,3 @@ +
+ Payload URL to send the event to. +
\ No newline at end of file diff --git a/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHook/help-secret.html b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHook/help-secret.html new file mode 100644 index 000000000..d1382b6aa --- /dev/null +++ b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHook/help-secret.html @@ -0,0 +1,3 @@ +
+ If secret is provided, event payload hash will be verified in TFS service. Recommended to add secret always for webhook. +
\ No newline at end of file diff --git a/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHook/help-webhookName.html b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHook/help-webhookName.html new file mode 100644 index 000000000..115aa92ab --- /dev/null +++ b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHook/help-webhookName.html @@ -0,0 +1,3 @@ +
+ Provide a name for the Webhook. +
\ No newline at end of file diff --git a/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHookAction/config.groovy b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHookAction/config.groovy new file mode 100644 index 000000000..369a4eb34 --- /dev/null +++ b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHookAction/config.groovy @@ -0,0 +1,9 @@ +package hudson.plugins.tfs.rm.ReleaseWebHookAction; + +def f = namespace(lib.FormTagLib); + +f.entry(title: _("Release Webhooks"), + field: "webHookReferences") { + + f.repeatableProperty(field: "webHookReferences") + } diff --git a/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHookReference/config.groovy b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHookReference/config.groovy new file mode 100644 index 000000000..cb20a0277 --- /dev/null +++ b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHookReference/config.groovy @@ -0,0 +1,13 @@ +package hudson.plugins.tfs.rm.ReleaseWebHookAction; + +def f = namespace(lib.FormTagLib); + +f.entry(title: _("Release Webhook"), field: "webHookName") { + f.select(); +} + +f.entry { + div(align: "right") { + f.repeatableDeleteButton() + } +} diff --git a/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHookReference/help-webHookName.html b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHookReference/help-webHookName.html new file mode 100644 index 000000000..855a9ea50 --- /dev/null +++ b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHookReference/help-webHookName.html @@ -0,0 +1,3 @@ +
+ Select a webhook. Upon job completion event will be send to the selected webhook's payload url. +
\ No newline at end of file diff --git a/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHookSummaryAction/summary.jelly b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHookSummaryAction/summary.jelly new file mode 100644 index 000000000..20e884735 --- /dev/null +++ b/tfs/src/main/resources/hudson/plugins/tfs/rm/ReleaseWebHookSummaryAction/summary.jelly @@ -0,0 +1,30 @@ + + + + Release WebHook status + ${status.status} + + + + + + + + + + + + + + + +
+ Success + Failure + + ${status.payloadUrl} +
+ + Failure reason: ${endIndex} ${status.errorMessage.substring(0, status.errorMessage.length() > 3000 ? 3000 : status.errorMessage.length())}Succeeded
+
+
From a8e3dd69ef8270d885649d46aab900ca4e097ab1 Mon Sep 17 00:00:00 2001 From: Kalyan Date: Sat, 8 Sep 2018 23:07:38 -0700 Subject: [PATCH 21/42] Adding release webhook configuration to global configuration (#208) * Adding release webhook configuration to global configuration * Fixed casing in the url --- .../plugins/tfs/JenkinsEventNotifier.java | 28 ++++++++++++------- .../plugins/tfs/TeamEventsEndpoint.java | 12 ++++++++ .../plugins/tfs/TeamPluginGlobalConfig.java | 15 +++++++++- .../plugins/tfs/rm/ReleaseWebHookHelper.java | 15 +++++----- .../tfs/TeamPluginGlobalConfig/config.groovy | 5 ++++ .../help-releaseWebhookConfigurations.html | 3 ++ 6 files changed, 60 insertions(+), 18 deletions(-) create mode 100644 tfs/src/main/resources/hudson/plugins/tfs/TeamPluginGlobalConfig/help-releaseWebhookConfigurations.html diff --git a/tfs/src/main/java/hudson/plugins/tfs/JenkinsEventNotifier.java b/tfs/src/main/java/hudson/plugins/tfs/JenkinsEventNotifier.java index e718c223b..e5ee69441 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/JenkinsEventNotifier.java +++ b/tfs/src/main/java/hudson/plugins/tfs/JenkinsEventNotifier.java @@ -60,7 +60,7 @@ public static void sendJobCompletionEvent(final JSONObject payload) { final JobCompletionEventArgs args = new JobCompletionEventArgs( connectionParameters.getConnectionKey(), jsonPayload, - getPayloadSignature(connectionParameters, jsonPayload)); + getPayloadSignature(connectionParameters.getConnectionSignature(), jsonPayload)); client.sendJobCompletionEvent(args); } catch (final Exception e) { log.warning("ERROR: sendJobCompletionEvent: (collection=" + c.getCollectionUrl() + ") " + e.getMessage()); @@ -154,6 +154,23 @@ public static String getApiJson(final String url) { } } + /** + * Calculates the payload hash. + * @param secret + * @param payload + * @return + * @throws NoSuchAlgorithmException + * @throws InvalidKeyException + * @throws UnsupportedEncodingException + */ + public static String getPayloadSignature(final String secret, final String payload) + throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException { + final SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(ENCODING), "HmacSHA1"); + final Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(signingKey); + return toHexString(mac.doFinal(payload.getBytes(ENCODING))); + } + private static String urlCombine(final String url, final String... parts) { final StringBuilder sb = new StringBuilder(); if (url != null) { @@ -170,15 +187,6 @@ private static String urlCombine(final String url, final String... parts) { return sb.toString(); } - private static String getPayloadSignature(final ConnectionParameters connectionParameters, final String payload) - throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException { - final String key = connectionParameters.getConnectionSignature(); - final SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(ENCODING), "HmacSHA1"); - final Mac mac = Mac.getInstance("HmacSHA1"); - mac.init(signingKey); - return toHexString(mac.doFinal(payload.getBytes(ENCODING))); - } - private static String toHexString(final byte[] bytes) { final Formatter formatter = new Formatter(); for (final byte b : bytes) { diff --git a/tfs/src/main/java/hudson/plugins/tfs/TeamEventsEndpoint.java b/tfs/src/main/java/hudson/plugins/tfs/TeamEventsEndpoint.java index 3912c53f3..e73ca0a74 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/TeamEventsEndpoint.java +++ b/tfs/src/main/java/hudson/plugins/tfs/TeamEventsEndpoint.java @@ -12,6 +12,7 @@ import hudson.plugins.tfs.model.GitPushEvent; import hudson.plugins.tfs.model.PingHookEvent; import hudson.plugins.tfs.model.servicehooks.Event; +import hudson.plugins.tfs.rm.ConnectReleaseWebHookEvent; import hudson.plugins.tfs.telemetry.TelemetryHelper; import hudson.plugins.tfs.util.EndpointHelper; import hudson.plugins.tfs.util.MediaType; @@ -62,6 +63,7 @@ public class TeamEventsEndpoint implements UnprotectedRootAction { eventMap.put("gitPullRequestMerged", new GitPullRequestMergedEvent.Factory()); eventMap.put("gitPush", new GitPushEvent.Factory()); eventMap.put("connect", new ConnectHookEvent.Factory()); + eventMap.put("rmWebhook", new ConnectReleaseWebHookEvent.Factory()); HOOK_EVENT_FACTORIES_BY_NAME = Collections.unmodifiableMap(eventMap); } @@ -225,6 +227,16 @@ public void doConnect( dispatch(request, response, body); } + @RequirePOST + public void doRmwebhook( + final StaplerRequest request, + final StaplerResponse response, + @StringBodyParameter @Nonnull final String body) { + // Send telemetry + TelemetryHelper.sendEvent("team-events-rmwebhook", new TelemetryHelper.PropertyMapBuilder().build()); + dispatch(request, response, body); + } + public static T findTrigger(final Job job, final Class tClass) { if (job instanceof ParameterizedJobMixIn.ParameterizedJob) { final ParameterizedJobMixIn.ParameterizedJob pJob = (ParameterizedJobMixIn.ParameterizedJob) job; diff --git a/tfs/src/main/java/hudson/plugins/tfs/TeamPluginGlobalConfig.java b/tfs/src/main/java/hudson/plugins/tfs/TeamPluginGlobalConfig.java index a35323d1c..1248dc396 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/TeamPluginGlobalConfig.java +++ b/tfs/src/main/java/hudson/plugins/tfs/TeamPluginGlobalConfig.java @@ -6,6 +6,7 @@ import hudson.plugins.tfs.model.DomainUserAccountMapper; import hudson.plugins.tfs.model.UserAccountMapper; import hudson.plugins.tfs.model.UserAccountMapperDescriptor; +import hudson.plugins.tfs.rm.ReleaseWebHook; import jenkins.model.GlobalConfiguration; import jenkins.model.Jenkins; import net.sf.json.JSONObject; @@ -27,7 +28,8 @@ public class TeamPluginGlobalConfig extends GlobalConfiguration { public static final TeamPluginGlobalConfig DEFAULT_CONFIG = new TeamPluginGlobalConfig(false); private List collectionConfigurations = new ArrayList(); - + private List releaseWebHookConfigurations = new ArrayList(); + private boolean configFolderPerNode; private boolean enableTeamPushTriggerForAllJobs; private boolean enableTeamStatusForAllJobs; @@ -64,6 +66,14 @@ public List getCollectionConfigurations() { public void setCollectionConfigurations(final List collectionConfigurations) { this.collectionConfigurations = collectionConfigurations; } + + public List getReleaseWebHookConfigurations() { + return this.releaseWebHookConfigurations; + } + + public void setReleaseWebHookConfigurations(final List releaseWebHookConfigurations) { + this.releaseWebHookConfigurations = releaseWebHookConfigurations; + } public boolean isConfigFolderPerNode() { return configFolderPerNode; @@ -108,6 +118,9 @@ public List getUserAccountMapperDescriptors() { public boolean configure(StaplerRequest req, JSONObject json) throws FormException { try { req.bindJSON(this, json); + + // stapler oddity, empty lists are not set on bean by "req.bindJSON(this, json)" + this.releaseWebHookConfigurations = req.bindJSONToList(ReleaseWebHook.class, json.get("releaseWebHookConfigurations")); } catch (final Exception e) { final String message = "Configuration error: " + e.getMessage(); diff --git a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookHelper.java b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookHelper.java index 2897a39f9..085c25b42 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookHelper.java +++ b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookHelper.java @@ -1,7 +1,10 @@ package hudson.plugins.tfs.rm; +import hudson.plugins.tfs.JenkinsEventNotifier; import hudson.plugins.tfs.TeamPluginGlobalConfig; -import java.util.ArrayList; +import java.io.UnsupportedEncodingException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; import java.util.List; /** @@ -22,8 +25,7 @@ public static List getReleaseWebHookConfigurations() { throw new InternalError("Cannot load TFS global configuration"); } - //return config.getReleaseWebHookConfigurations(); - return new ArrayList(); + return config.getReleaseWebHookConfigurations(); } /** @@ -36,7 +38,7 @@ public static void saveReleaseWebHookConfigurations(final List r throw new InternalError("Cannot load TFS global configuration"); } - //config.setReleaseWebHookConfigurations(releaseWebHooks); + config.setReleaseWebHookConfigurations(releaseWebHooks); config.save(); } @@ -46,8 +48,7 @@ public static void saveReleaseWebHookConfigurations(final List r * @param payload * @return */ - public static String getPayloadSignature(final String secret, final String payload) { - //JenkinsEventNotifier.getPayloadSignature(secret payload); - return null; + public static String getPayloadSignature(final String secret, final String payload) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException { + return JenkinsEventNotifier.getPayloadSignature(secret, payload); } } diff --git a/tfs/src/main/resources/hudson/plugins/tfs/TeamPluginGlobalConfig/config.groovy b/tfs/src/main/resources/hudson/plugins/tfs/TeamPluginGlobalConfig/config.groovy index 3a1f4bbda..48a82bfcb 100644 --- a/tfs/src/main/resources/hudson/plugins/tfs/TeamPluginGlobalConfig/config.groovy +++ b/tfs/src/main/resources/hudson/plugins/tfs/TeamPluginGlobalConfig/config.groovy @@ -33,4 +33,9 @@ f.section(title: descriptor.displayName) { f.checkbox (default: false) } } + f.entry(title: _("Release WebHooks"), + field: "releaseWebHookConfigurations") { + + f.repeatableProperty(field: "releaseWebHookConfigurations") + } } diff --git a/tfs/src/main/resources/hudson/plugins/tfs/TeamPluginGlobalConfig/help-releaseWebhookConfigurations.html b/tfs/src/main/resources/hudson/plugins/tfs/TeamPluginGlobalConfig/help-releaseWebhookConfigurations.html new file mode 100644 index 000000000..7b61f53e3 --- /dev/null +++ b/tfs/src/main/resources/hudson/plugins/tfs/TeamPluginGlobalConfig/help-releaseWebhookConfigurations.html @@ -0,0 +1,3 @@ +
+ Adds a Release Webhook. Project can refer to this Webhook in its post build action. When a build completes it will send the event payload to the payload url mentioned. +
From f7d6103b5b2edbf354895f713cf74da7a73ff931 Mon Sep 17 00:00:00 2001 From: mrflow Date: Mon, 24 Sep 2018 15:54:14 +0200 Subject: [PATCH 22/42] Improve VSTS Service Hook integration (#200) * Reduce code duplication and make PR feedback functional Deduplicate code paths used for feeding back status to VSTS/TFS. Further, set the "name" and "genre" fields to sane values: VSTS uses these fiels for configuration of branch policies. Empty strings are not permitted, thus this feature was broken on the part of tfs-plugin. Use the Jenkins root-URL as 'genre' and the job-name as 'name'. * Abort running build if a Pull-Request triggers a job If a Service Hook triggers a job, cancel all running builds with the same build cause. --- .../plugins/tfs/JenkinsEventNotifier.java | 48 ----------- .../tfs/listeners/JenkinsRunListener.java | 79 ++----------------- .../plugins/tfs/model/AbstractHookEvent.java | 9 ++- .../plugins/tfs/model/BuildCommand.java | 33 +++++++- .../plugins/tfs/model/TeamGitStatus.java | 27 +++++-- .../hudson/plugins/tfs/util/TeamStatus.java | 28 ++++++- 6 files changed, 93 insertions(+), 131 deletions(-) diff --git a/tfs/src/main/java/hudson/plugins/tfs/JenkinsEventNotifier.java b/tfs/src/main/java/hudson/plugins/tfs/JenkinsEventNotifier.java index e5ee69441..7bba6efdc 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/JenkinsEventNotifier.java +++ b/tfs/src/main/java/hudson/plugins/tfs/JenkinsEventNotifier.java @@ -1,11 +1,7 @@ package hudson.plugins.tfs; import hudson.plugins.tfs.model.ConnectionParameters; -import hudson.plugins.tfs.model.GitStatusContext; -import hudson.plugins.tfs.model.GitStatusState; import hudson.plugins.tfs.model.JobCompletionEventArgs; -import hudson.plugins.tfs.model.PullRequestMergeCommitCreatedEventArgs; -import hudson.plugins.tfs.model.TeamGitStatus; import hudson.plugins.tfs.util.TeamRestClient; import jenkins.model.Jenkins; import net.sf.json.JSONObject; @@ -19,11 +15,9 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.io.BufferedReader; -import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; -import java.net.MalformedURLException; import java.net.URI; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -68,46 +62,6 @@ public static void sendJobCompletionEvent(final JSONObject payload) { } } - /** - * Send the build status of VSTS pull request back to connected TFS/VSTS servers only for those jobs kicked off by PRs. - */ - public static void sendPullRequestBuildStatusEvent(final CommitParameterAction commitParameter, final GitStatusState buildState, final String buildDescription, final String targetUrl, final String jobFullPath) { - try { - if (commitParameter != null) { - if (commitParameter instanceof PullRequestParameterAction) { - final PullRequestParameterAction prpa = (PullRequestParameterAction) commitParameter; - final PullRequestMergeCommitCreatedEventArgs pullRequestMergeCommitCreatedEventArgs = prpa.getPullRequestMergeCommitCreatedEventArgs(); - sendPullRequestBuildStatusEvent(pullRequestMergeCommitCreatedEventArgs, buildState, buildDescription, targetUrl, jobFullPath); - } - } - } catch (final Exception e) { - log.warning("ERROR: sendPullRequestBuildStatusEvent fails due to exception: " + e.getMessage()); - } - return; - } - - /** - * Send the build status of VSTS pull request back to connected TFS/VSTS servers. - */ - public static void sendPullRequestBuildStatusEvent(final PullRequestMergeCommitCreatedEventArgs gitCodePushedEventArgs, final GitStatusState buildState, final String buildDescription, final String targetUrl, final String jobFullPath) { - try { - final TeamGitStatus status = new TeamGitStatus(); - status.state = buildState; - status.description = buildDescription; - status.targetUrl = targetUrl; - status.context = new GitStatusContext(" ", StringUtils.stripEnd(jobFullPath, "/")); - - final URI collectionUri = gitCodePushedEventArgs.collectionUri; - final TeamRestClient client = new TeamRestClient(collectionUri); - client.addPullRequestStatus(gitCodePushedEventArgs, status); - } catch (MalformedURLException e) { - log.warning("ERROR: sendPullRequestBuildStatusEvent fails due to MalformedURLException: " + e.getMessage()); - } catch (IOException e) { - log.warning("ERROR: sendPullRequestBuildStatusEvent fails due to IOException: " + e.getMessage()); - } - return; - } - /** * This is a helper method to get the JSON for a Jenkins object. * @param url @@ -195,6 +149,4 @@ private static String toHexString(final byte[] bytes) { return formatter.toString(); } - - } diff --git a/tfs/src/main/java/hudson/plugins/tfs/listeners/JenkinsRunListener.java b/tfs/src/main/java/hudson/plugins/tfs/listeners/JenkinsRunListener.java index 28dec0673..ac69a836c 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/listeners/JenkinsRunListener.java +++ b/tfs/src/main/java/hudson/plugins/tfs/listeners/JenkinsRunListener.java @@ -1,33 +1,24 @@ package hudson.plugins.tfs.listeners; import hudson.Extension; -import hudson.model.Action; -import hudson.model.Cause; -import hudson.model.CauseAction; -import hudson.model.Job; -import hudson.model.Result; import hudson.model.Run; import hudson.model.TaskListener; import hudson.model.listeners.RunListener; -import hudson.plugins.tfs.CommitParameterAction; -import hudson.plugins.tfs.JenkinsEventNotifier; -import hudson.plugins.tfs.TeamPushCause; + +import hudson.plugins.tfs.TeamCompletedStatusPostBuildAction; +import hudson.plugins.tfs.TeamPendingStatusBuildStep; import hudson.plugins.tfs.UnsupportedIntegrationAction; -import hudson.plugins.tfs.model.GitStatusState; -import java.util.List; -import net.sf.json.JSONObject; import javax.annotation.Nonnull; import java.util.logging.Logger; + /** * This class listens to the events of every Jenkins run instance. - * Completed runs fire an event back to the JenkinsEventNotifier. */ @Extension public class JenkinsRunListener extends RunListener { protected static final Logger log = Logger.getLogger(JenkinsRunListener.class.getName()); - private static final String DEFAULT_RUN_CONTEXT = "Jenkins PR build"; public JenkinsRunListener() { log.fine("JenkinsRunListener: constructor"); @@ -40,11 +31,8 @@ public void onDeleted(final Run run) { @Override public void onStarted(final Run run, final TaskListener listener) { if (UnsupportedIntegrationAction.isSupported(run, listener)) { - Job currJob = run.getParent(); - final String targetUrl = currJob.getAbsoluteUrl() + (currJob.getNextBuildNumber() - 1); - final CommitParameterAction commitParameter = run.getAction(CommitParameterAction.class); - final String context = getRunContext(run) + " started"; - JenkinsEventNotifier.sendPullRequestBuildStatusEvent(commitParameter, GitStatusState.Pending, context, targetUrl, currJob.getAbsoluteUrl()); + final TeamPendingStatusBuildStep step = new TeamPendingStatusBuildStep(); + step.perform(run, listener); } } @@ -55,60 +43,9 @@ public void onFinalized(final Run run) { @Override public void onCompleted(final Run run, @Nonnull final TaskListener listener) { log.info("onCompleted: " + run.toString()); - - GitStatusState runGitState; - final Result runResult = run.getResult(); - if (runResult != null && runResult.isBetterOrEqualTo(Result.SUCCESS)) { - runGitState = GitStatusState.Succeeded; - } else { - runGitState = GitStatusState.Failed; - } - if (UnsupportedIntegrationAction.isSupported(run, listener)) { - Job currJob = run.getParent(); - final String runDescription = run.getDescription(); - final String targetUrl = currJob.getAbsoluteUrl() + (currJob.getNextBuildNumber() - 1); - final CommitParameterAction commitParameter = run.getAction(CommitParameterAction.class); - final String context = getRunContext(run) + " completed"; - JenkinsEventNotifier.sendPullRequestBuildStatusEvent(commitParameter, runGitState, context, targetUrl, currJob.getAbsoluteUrl()); - } - - JSONObject json = createJsonFromRun(run); - final String payload = JenkinsEventNotifier.getApiJson(run.getUrl()); - if (payload != null) { - json = JSONObject.fromObject(payload); - } - - json.put("name", run.getParent().getDisplayName()); - json.put("startedBy", getStartedBy(run)); - - JenkinsEventNotifier.sendJobCompletionEvent(json); - } - - private String getStartedBy(final Run run) { - final Cause.UserIdCause cause = (Cause.UserIdCause) run.getCause(Cause.UserIdCause.class); - String startedBy = ""; - if (cause != null && cause.getUserId() != null) { - startedBy = cause.getUserId(); - } - return startedBy; - } - - private JSONObject createJsonFromRun(final Run run) { - return new JSONObject(); - } - - private String getRunContext(final Run run) { - List actionList = run.getAllActions(); - for (final Action currAction : actionList) { - if (currAction instanceof CauseAction) { - for (final Cause currCause : ((CauseAction) currAction).getCauses()) { - if (currCause instanceof TeamPushCause) { - return ((TeamPushCause) currCause).getRunContext(); - } - } - } + final TeamCompletedStatusPostBuildAction step = new TeamCompletedStatusPostBuildAction(); + step.perform(run, listener); } - return DEFAULT_RUN_CONTEXT; } } diff --git a/tfs/src/main/java/hudson/plugins/tfs/model/AbstractHookEvent.java b/tfs/src/main/java/hudson/plugins/tfs/model/AbstractHookEvent.java index f531f17e6..5a7cd93a3 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/model/AbstractHookEvent.java +++ b/tfs/src/main/java/hudson/plugins/tfs/model/AbstractHookEvent.java @@ -16,7 +16,6 @@ import hudson.plugins.git.GitStatus; import hudson.plugins.git.UserRemoteConfig; import hudson.plugins.git.extensions.impl.IgnoreNotifyCommit; -import hudson.plugins.tfs.JenkinsEventNotifier; import hudson.plugins.tfs.SafeParametersAction; import hudson.plugins.tfs.TeamEventsEndpoint; import hudson.plugins.tfs.TeamGlobalStatusAction; @@ -26,6 +25,7 @@ import hudson.plugins.tfs.TeamPushTrigger; import hudson.plugins.tfs.model.servicehooks.Event; import hudson.plugins.tfs.util.ActionHelper; +import hudson.plugins.tfs.util.TeamStatus; import hudson.plugins.tfs.util.UriHelper; import hudson.scm.SCM; import hudson.security.ACL; @@ -43,6 +43,7 @@ import org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition; import org.jenkinsci.plugins.workflow.flow.FlowDefinition; +import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; @@ -145,7 +146,11 @@ GitStatus.ResponseContributor triggerJob(final GitCodePushedEventArgs gitCodePus final Action[] actionArray = ActionHelper.create(actionsWithSafeParams, causeAction); scmTriggerItem.scheduleBuild2(quietPeriod, actionArray); if (gitCodePushedEventArgs instanceof PullRequestMergeCommitCreatedEventArgs) { - JenkinsEventNotifier.sendPullRequestBuildStatusEvent((PullRequestMergeCommitCreatedEventArgs) gitCodePushedEventArgs, GitStatusState.Pending, "Jenkins PR build queued", targetUrl, job.getAbsoluteUrl()); + try { + TeamStatus.createFromJob((PullRequestMergeCommitCreatedEventArgs) gitCodePushedEventArgs, job); + } catch (IOException ex) { + LOGGER.warning("Could not create TeamStatus: " + ex.toString()); + } } return new TeamEventsEndpoint.ScheduledResponseContributor(project); diff --git a/tfs/src/main/java/hudson/plugins/tfs/model/BuildCommand.java b/tfs/src/main/java/hudson/plugins/tfs/model/BuildCommand.java index c35d921df..9aec303ad 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/model/BuildCommand.java +++ b/tfs/src/main/java/hudson/plugins/tfs/model/BuildCommand.java @@ -7,12 +7,14 @@ import hudson.model.BuildableItem; import hudson.model.Cause; import hudson.model.CauseAction; +import hudson.model.Executor; import hudson.model.Job; import hudson.model.ParameterDefinition; import hudson.model.ParameterValue; import hudson.model.ParametersAction; import hudson.model.ParametersDefinitionProperty; import hudson.model.Queue; +import hudson.model.Run; import hudson.model.SimpleParameterDefinition; import hudson.model.queue.ScheduleResult; import hudson.plugins.tfs.CommitParameterAction; @@ -25,6 +27,7 @@ import hudson.plugins.tfs.model.servicehooks.Event; import hudson.plugins.tfs.util.ActionHelper; import hudson.plugins.tfs.util.MediaType; +import hudson.util.RunList; import jenkins.model.Jenkins; import jenkins.util.TimeDuration; import net.sf.json.JSONArray; @@ -83,7 +86,7 @@ public String getSampleRequestPayload() { } } - protected JSONObject innerPerform(final BuildableItem buildableItem, final TimeDuration delay, final List extraActions) { + protected JSONObject innerPerform(final Job job, final BuildableItem buildableItem, final TimeDuration delay, final List extraActions) { final JSONObject result = new JSONObject(); final Jenkins jenkins = Jenkins.getActiveInstance(); @@ -91,6 +94,11 @@ protected JSONObject innerPerform(final BuildableItem buildableItem, final TimeD final Cause cause = new Cause.UserIdCause(); final CauseAction causeAction = new CauseAction(cause); final Action[] actionArray = ActionHelper.create(extraActions, causeAction); + for (Action a : extraActions) { + if (a instanceof TeamPullRequestMergedDetailsAction) { + cancelPreviousPullRequestBuilds(job, (TeamPullRequestMergedDetailsAction) a, queue); + } + } final ScheduleResult scheduleResult = queue.schedule2(buildableItem, delay.getTime(), actionArray); final Queue.Item item = scheduleResult.getItem(); if (item != null) { @@ -209,7 +217,28 @@ else if (name.equals(PULL_REQUEST_ID) && pullRequestId != null && d instanceof S actions.add(action); } - return innerPerform(buildableItem, delay, actions); + return innerPerform(job, buildableItem, delay, actions); + } + + private void cancelPreviousPullRequestBuilds(Job job, TeamPullRequestMergedDetailsAction pullReqeuestMergedDetails, Queue queue) { + RunList allBuilds = job.getBuilds(); + + for (Run run : allBuilds) { + TeamPullRequestMergedDetailsAction cause = run.getAction(TeamPullRequestMergedDetailsAction.class); + if (cause != null && run.isBuilding()) { + if (pullReqeuestMergedDetails instanceof TeamPullRequestMergedDetailsAction) { + LOGGER.info("Canceling previously triggered Job: " + run.getFullDisplayName()); + + Executor executor = run.getExecutor(); + if (executor != null) + executor.doStop(); + + Queue.Item item = queue.getItem(run.getQueueId()); + if (item != null) + queue.cancel(item); + } + } + } } static void contributeTeamBuildParameterActions(final Map teamBuildParameters, final List actions) { diff --git a/tfs/src/main/java/hudson/plugins/tfs/model/TeamGitStatus.java b/tfs/src/main/java/hudson/plugins/tfs/model/TeamGitStatus.java index 2bf763f53..83aeb2062 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/model/TeamGitStatus.java +++ b/tfs/src/main/java/hudson/plugins/tfs/model/TeamGitStatus.java @@ -4,7 +4,9 @@ import hudson.model.Job; import hudson.model.Result; import hudson.model.Run; +import jenkins.model.Jenkins; import net.sf.json.JSONObject; +import org.apache.commons.lang.StringUtils; import javax.annotation.Nonnull; import java.util.Collections; @@ -36,19 +38,32 @@ public static TeamGitStatus fromRun(@Nonnull final Run run) { if (result == null) { status.state = GitStatusState.Pending; status.description = status.state.toString(); - } - else { + } else { status.state = RESULT_TO_STATE.get(result); status.description = result.toString(); } + final Job job = run.getParent(); + status.description = job.getDisplayName() + run.getDisplayName() + ": " + status.description; status.targetUrl = run.getAbsoluteUrl(); - final Job project = run.getParent(); - final String runDisplayName = run.getDisplayName(); - final String projectDisplayName = project.getDisplayName(); - status.context = new GitStatusContext(runDisplayName, projectDisplayName); + status.context = getStatusContext(job); return status; } + public static TeamGitStatus fromJob(@Nonnull final Job job) { + final TeamGitStatus status = new TeamGitStatus(); + status.state = GitStatusState.Pending; + status.description = "Jenkins Job " + job.getDisplayName() + " queued"; + status.targetUrl = job.getAbsoluteUrl(); + status.context = getStatusContext(job); + return status; + } + + private static GitStatusContext getStatusContext(@Nonnull final Job job) { + final String instanceUrl = StringUtils.stripEnd(Jenkins.getInstance().getRootUrl(), "/"); + final String projectDisplayName = job.getParent().getFullName() + "/" + job.getDisplayName(); + return new GitStatusContext(projectDisplayName, instanceUrl); + } + public String toJson() { final JSONObject jsonObject = JSONObject.fromObject(this); final String result = jsonObject.toString(); diff --git a/tfs/src/main/java/hudson/plugins/tfs/util/TeamStatus.java b/tfs/src/main/java/hudson/plugins/tfs/util/TeamStatus.java index b86326190..4e7c3e904 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/util/TeamStatus.java +++ b/tfs/src/main/java/hudson/plugins/tfs/util/TeamStatus.java @@ -1,5 +1,6 @@ package hudson.plugins.tfs.util; +import hudson.model.Job; import hudson.model.Run; import hudson.model.TaskListener; import hudson.plugins.tfs.CommitParameterAction; @@ -38,6 +39,7 @@ public static void createFromRun(@Nonnull final Run run, @Nonnull final Ta final CommitParameterAction commitParameter = run.getAction(CommitParameterAction.class); final GitCodePushedEventArgs gitCodePushedEventArgs; final PullRequestMergeCommitCreatedEventArgs pullRequestMergeCommitCreatedEventArgs; + if (commitParameter != null) { gitCodePushedEventArgs = commitParameter.getGitCodePushedEventArgs(); if (commitParameter instanceof PullRequestParameterAction) { @@ -52,8 +54,6 @@ public static void createFromRun(@Nonnull final Run run, @Nonnull final Ta } final URI collectionUri = gitCodePushedEventArgs.collectionUri; - final TeamRestClient client = new TeamRestClient(collectionUri); - final TeamGitStatus status = TeamGitStatus.fromRun(run); // Send telemetry @@ -63,6 +63,30 @@ public static void createFromRun(@Nonnull final Run run, @Nonnull final Ta .pair("status", status.state.toString()) .build()); + addStatus(pullRequestMergeCommitCreatedEventArgs, status); + } + + /** + * Create status for a (queued) Job. + */ + public static void createFromJob(final PullRequestMergeCommitCreatedEventArgs pullRequestMergeCommitCreatedEventArgs, final Job job) throws IOException { + final TeamGitStatus status = TeamGitStatus.fromJob(job); + + addStatus(pullRequestMergeCommitCreatedEventArgs, status); + } + + private static void addStatus(final PullRequestMergeCommitCreatedEventArgs gitCodePushedEventArgs, final TeamGitStatus status) throws IOException { + final PullRequestMergeCommitCreatedEventArgs pullRequestMergeCommitCreatedEventArgs; + + if (gitCodePushedEventArgs instanceof PullRequestMergeCommitCreatedEventArgs) { + pullRequestMergeCommitCreatedEventArgs = (PullRequestMergeCommitCreatedEventArgs) gitCodePushedEventArgs; + } else { + pullRequestMergeCommitCreatedEventArgs = null; + } + + final URI collectionUri = gitCodePushedEventArgs.collectionUri; + final TeamRestClient client = new TeamRestClient(collectionUri); + // TODO: when code is pushed and polling happens, are we sure we built against the requested commit? if (pullRequestMergeCommitCreatedEventArgs != null) { if (pullRequestMergeCommitCreatedEventArgs.iterationId == -1) { From c3f4f90972e686e0e4f50e20b2b78abeddb7a589 Mon Sep 17 00:00:00 2001 From: mrflow Date: Mon, 1 Oct 2018 15:28:34 +0200 Subject: [PATCH 23/42] BuildCommand: abort a build only if it was caused by the same PR (#211) --- tfs/src/main/java/hudson/plugins/tfs/model/BuildCommand.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tfs/src/main/java/hudson/plugins/tfs/model/BuildCommand.java b/tfs/src/main/java/hudson/plugins/tfs/model/BuildCommand.java index 9aec303ad..d1e193d6e 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/model/BuildCommand.java +++ b/tfs/src/main/java/hudson/plugins/tfs/model/BuildCommand.java @@ -226,7 +226,8 @@ private void cancelPreviousPullRequestBuilds(Job job, TeamPullRequestMergedDetai for (Run run : allBuilds) { TeamPullRequestMergedDetailsAction cause = run.getAction(TeamPullRequestMergedDetailsAction.class); if (cause != null && run.isBuilding()) { - if (pullReqeuestMergedDetails instanceof TeamPullRequestMergedDetailsAction) { + if (cause instanceof TeamPullRequestMergedDetailsAction && + cause.gitPullRequest.getPullRequestId() == pullReqeuestMergedDetails.gitPullRequest.getPullRequestId()) { LOGGER.info("Canceling previously triggered Job: " + run.getFullDisplayName()); Executor executor = run.getExecutor(); From 497a6c39fac42f4f79bd257271ea15f04da14983 Mon Sep 17 00:00:00 2001 From: Kalyan Date: Fri, 5 Oct 2018 00:22:13 +0530 Subject: [PATCH 24/42] Bumping up version to 5.142.0 (#212) --- ReleaseNotes.md | 4 ++-- pom.xml | 2 +- tfs-sdk/pom.xml | 2 +- tfs/pom.xml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 90f7018d0..e10e43caa 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,8 +1,8 @@ -These notes are for release **(to be determined)**. +These notes are for release **5.142.0**. Other releases and their notes can be found at the [tfs-plugin GitHub Releases](https://github.com/jenkinsci/tfs-plugin/releases) page. * Major - * TODO + * Added ReleaseWebHook support. Jenkins build completion will send an event to the specified WebHook url which will trigger the releases for release definitions which has this Jenkins job as artifact. * Minor: * TODO diff --git a/pom.xml b/pom.xml index 662926e8a..2ddd57258 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ tfs-parent pom Team Foundation Server Plug-in parent - 5.139.2-SNAPSHOT + 5.142.0-SNAPSHOT 1.642.3 diff --git a/tfs-sdk/pom.xml b/tfs-sdk/pom.xml index 8a8d09ef3..7652e5cc7 100644 --- a/tfs-sdk/pom.xml +++ b/tfs-sdk/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.139.2-SNAPSHOT + 5.142.0-SNAPSHOT tfs-sdk diff --git a/tfs/pom.xml b/tfs/pom.xml index d5d63072b..38b083e46 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -3,13 +3,13 @@ org.jenkins-ci.plugins tfs-parent - 5.139.2-SNAPSHOT + 5.142.0-SNAPSHOT tfs hpi Team Foundation Server Plug-in - 5.139.2-SNAPSHOT + 5.142.0-SNAPSHOT http://wiki.jenkins-ci.org/display/JENKINS/Team+Foundation+Server+Plugin From ce3c29d4b9556dc225cfcd7c08328d663cf570b4 Mon Sep 17 00:00:00 2001 From: Jason Prickett Date: Thu, 4 Oct 2018 19:11:02 +0000 Subject: [PATCH 25/42] [maven-release-plugin] prepare release tfs-5.142.0 --- pom.xml | 4 ++-- tfs-sdk/pom.xml | 2 +- tfs/pom.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 2ddd57258..cb2f9771e 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ tfs-parent pom Team Foundation Server Plug-in parent - 5.142.0-SNAPSHOT + 5.142.0 1.642.3 @@ -69,7 +69,7 @@ scm:git:https://github.com/jenkinsci/tfs-plugin.git scm:git:https://github.com/jenkinsci/tfs-plugin.git - HEAD + tfs-5.142.0 diff --git a/tfs-sdk/pom.xml b/tfs-sdk/pom.xml index 7652e5cc7..6c6d70480 100644 --- a/tfs-sdk/pom.xml +++ b/tfs-sdk/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.142.0-SNAPSHOT + 5.142.0 tfs-sdk diff --git a/tfs/pom.xml b/tfs/pom.xml index 38b083e46..1b802f379 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -3,13 +3,13 @@ org.jenkins-ci.plugins tfs-parent - 5.142.0-SNAPSHOT + 5.142.0 tfs hpi Team Foundation Server Plug-in - 5.142.0-SNAPSHOT + 5.142.0 http://wiki.jenkins-ci.org/display/JENKINS/Team+Foundation+Server+Plugin From 40db54ce17c414d8e3f2dd0fbca7090735956bd9 Mon Sep 17 00:00:00 2001 From: Jason Prickett Date: Thu, 4 Oct 2018 19:11:10 +0000 Subject: [PATCH 26/42] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- tfs-sdk/pom.xml | 2 +- tfs/pom.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index cb2f9771e..2ddd57258 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ tfs-parent pom Team Foundation Server Plug-in parent - 5.142.0 + 5.142.0-SNAPSHOT 1.642.3 @@ -69,7 +69,7 @@ scm:git:https://github.com/jenkinsci/tfs-plugin.git scm:git:https://github.com/jenkinsci/tfs-plugin.git - tfs-5.142.0 + HEAD diff --git a/tfs-sdk/pom.xml b/tfs-sdk/pom.xml index 6c6d70480..7652e5cc7 100644 --- a/tfs-sdk/pom.xml +++ b/tfs-sdk/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.142.0 + 5.142.0-SNAPSHOT tfs-sdk diff --git a/tfs/pom.xml b/tfs/pom.xml index 1b802f379..38b083e46 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -3,13 +3,13 @@ org.jenkins-ci.plugins tfs-parent - 5.142.0 + 5.142.0-SNAPSHOT tfs hpi Team Foundation Server Plug-in - 5.142.0 + 5.142.0-SNAPSHOT http://wiki.jenkins-ci.org/display/JENKINS/Team+Foundation+Server+Plugin From 8486127d5f011f1daaeb4f6f87a49e65e243a3ad Mon Sep 17 00:00:00 2001 From: Kalyan Date: Fri, 5 Oct 2018 09:04:35 +0530 Subject: [PATCH 27/42] Clear out Release Notes for next release. (#213) --- ReleaseNotes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index e10e43caa..90f7018d0 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,8 +1,8 @@ -These notes are for release **5.142.0**. +These notes are for release **(to be determined)**. Other releases and their notes can be found at the [tfs-plugin GitHub Releases](https://github.com/jenkinsci/tfs-plugin/releases) page. * Major - * Added ReleaseWebHook support. Jenkins build completion will send an event to the specified WebHook url which will trigger the releases for release definitions which has this Jenkins job as artifact. + * TODO * Minor: * TODO From 9643446f994777011e5ac59d26b108ca42bc79e8 Mon Sep 17 00:00:00 2001 From: Brian Benz Date: Mon, 29 Jul 2019 13:04:35 -0700 Subject: [PATCH 28/42] Add Azure DevOps to repo docs (#225) --- README.md | 116 +++++++++++++++++++++++++-------------------------- Releasing.md | 6 +-- Testing.md | 2 +- 3 files changed, 62 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index d99113c03..a28231b35 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -Team Foundation Server plugin for Jenkins +Azure DevOps and Team Foundation Server plugin for Jenkins ============================ Copyright © Erik Ramfelt, Olivier Dagenais, CloudBees, Inc. and others. Licensed under [MIT Licence]. ## Summary -This plugin integrates [Team Foundation Version Control] (also known as TFVC) and Git to Jenkins by connecting to Team Foundation Server (TFS) and Visual Studio Team Services (Team Services). It also lets you trigger a release in Visual Studio Team Services, through a post-build step in Jenkins. +This plugin triggers a release in Azure DevOps, through a post-build step in Jenkins. It also integrates [Team Foundation Version Control] (also known as TFVC) and Git to Jenkins by connecting to Team Foundation Server (TFS). ## Quick links * The legacy [wiki] page on the Jenkins Confluence instance @@ -14,52 +14,52 @@ This plugin integrates [Team Foundation Version Control] (also known as TFVC) an ## What can you do with it? -That depends on which version control system you use in TFS/Team Services: +That depends on which version control system you use in Azure DevOps/TFS: ### Team Foundation Version Control -Allows you to use a TFVC repository as an SCM in Jenkins jobs. At the moment, this plugin supports: +Allows you to use a TFVC repository as an SCM in a Jenkins jobs. At the moment, this plugin supports: * Retrieving read-only copies of files and folders from a TFVC repository. * Polling a TFVC repository to automatically start builds when there are changes. * Links from the Jenkins change sets to the TFVC repository web interface. _(Also known as a repository browser)_ * Creating a label in the TFVC repository -The plugin will automatically create a workspace in TFS/Team Services and map a work folder (in the Jenkins workspace) to it. +The plugin will automatically create a workspace in TFS/Azure DevOps and map a work folder (in the Jenkins workspace) to it. ### Git -The TFS plug-in for Jenkins adds some features to better integrate with Git repositories hosted in TFS/Team Services: +The TFS plug-in for Jenkins adds some features to better integrate with Git repositories hosted in TFS/Azure DevOps: * A push trigger, to request builds of specific commits in Git repositories without needing to schedule SCM polling - * Instead of adding the **Build when a change is pushed to TFS/Team Services** trigger to every job, you can also check the checkbox next to **Enable Push Trigger for all jobs** in the Jenkins global configuration. Individual jobs can still opt-out of this global opt-in by enabling the **Poll SCM** trigger and checking its **Ignore post-commit hooks** checkbox. -* A build step that adds a "build pending" status to the associated pull request and/or commit in TFS/Team Services - * Instead of adding the **Set build pending status in TFS/Team Services** build step to every job, you can also check the checkbox next to **Enable Team Status for all jobs** in the Jenkins global configuration. -* A post-build action that add a "build completed" status to the associated pull request and/or commit in TFS/Team Services - * Instead of adding the **Set build completion status in TFS/Team Services** post-build action to every job, you can also check the checkboxbox next to **Enable Team Status for all jobs** in the Jenkins global configuration. -* A link to (and summary information about) the associated TFS/Team Services build that triggered the Jenkins build. -* A link to (and summary information about) the associated TFS/Team Services pull request that triggered the Jenkins build. - * Links to associated TFS/Team Services work items. - * Associated TFS/Team Services work items link back to the Jenkins build. + * Instead of adding the **Build when a change is pushed to TFS/Azure DevOps** trigger to every job, you can also check the checkbox next to **Enable Push Trigger for all jobs** in the Jenkins global configuration. Individual jobs can still opt-out of this global opt-in by enabling the **Poll SCM** trigger and checking its **Ignore post-commit hooks** checkbox. +* A build step that adds a "build pending" status to the associated pull request and/or commit in TFS/Azure DevOps + * Instead of adding the **Set build pending status in TFS/Azure DevOps** build step to every job, you can also check the checkbox next to **Enable Team Status for all jobs** in the Jenkins global configuration. +* A post-build action that add a "build completed" status to the associated pull request and/or commit in TFS/Azure DevOps + * Instead of adding the **Set build completion status in TFS/Azure DevOps** post-build action to every job, you can also check the checkboxbox next to **Enable Team Status for all jobs** in the Jenkins global configuration. +* A link to (and summary information about) the associated TFS/Azure DevOps build that triggered the Jenkins build. +* A link to (and summary information about) the associated TFS/Azure DevOps pull request that triggered the Jenkins build. + * Links to associated TFS/Azure DevOps work items. + * Associated TFS/Azure DevOps work items link back to the Jenkins build. There are two possibilities to trigger a build with SCM Changeset: * TFS plugin - Please refer to the [Webhooks with Visual Studio Team Services](https://www.visualstudio.com/en-us/docs/service-hooks/services/webhooks) page for instructions on configuring the integration in TFS. Available endpoints can be found under [http://yourJenkins/team-events](http://yourJenkins/team-events) + Please refer to the [Webhooks with Azure DevOps](https://www.visualstudio.com/en-us/docs/service-hooks/services/webhooks) page for instructions on configuring the integration in TFS. Available endpoints can be found under [http://yourJenkins/team-events](http://yourJenkins/team-events) * Git plugin You have to enable Git SCM polling to receive commit notifications - Please refer to the [Jenkins with Visual Studio Team Services](https://www.visualstudio.com/en-us/docs/service-hooks/services/jenkins) page for instructions on configuring the integration in TFS. + Please refer to the [Jenkins with Azure DevOps](https://www.visualstudio.com/en-us/docs/service-hooks/services/jenkins) page for instructions on configuring the integration in TFS. # Supported versions The following sub-sections list the various versions of software that were tested and are thus supported. The plugin might work with other versions, they just haven't been tested. -## Team Foundation Server (TFS) / Visual Studio Team Services (Team Services) +## Team Foundation Server (TFS) / Azure DevOps (Azure DevOps) -The following table indicates compatibility and support for versions of TFS and Team Services. +The following table indicates compatibility and support for versions of TFS and Azure DevOps. > Version | Supported by the TFS plugin? | Mainstream Support End Date > ------- | ------ | --------------------------- -> [Visual Studio Team Services] | :white_check_mark: | n/a +> [Azure DevOps] | :white_check_mark: | n/a > Visual Studio Team Foundation Server 2017 | :white_check_mark: | [2022/01/11](https://support.microsoft.com/en-us/lifecycle/search?alpha=Visual%20Studio%20Team%20Foundation%20Server%202017) > Visual Studio Team Foundation Server 2015 | :white_check_mark: | [2020/10/13](https://support.microsoft.com/en-us/lifecycle?p1=18576) > Microsoft Visual Studio Team Foundation Server 2013 | :white_check_mark: | [2019/04/09](https://support.microsoft.com/en-us/lifecycle?p1=17358) @@ -68,11 +68,11 @@ The following table indicates compatibility and support for versions of TFS and > Microsoft Visual Studio Team System 2008 Team Foundation Server | :x: | :warning: [2013/04/09](https://support.microsoft.com/en-us/lifecycle?p1=13083) > Microsoft Visual Studio 2005 Team Foundation Server | :x: | :warning: [2011/07/12](https://support.microsoft.com/en-us/lifecycle?p1=10449) -Whereas for **Trigger release in TFS/Team Services** post build action, only following table is supported: +Whereas for **Trigger release in TFS/Azure DevOps** post build action, only following table is supported: > Version | Supported by the TFS plugin? | Mainstream Support End Date > ------- | ------ | --------------------------- -> [Visual Studio Team Services] | :white_check_mark: | n/a +> [Azure DevOps] | :white_check_mark: | n/a > [Team Foundation Server "15" RC1] | :white_check_mark: | n/a ## Operating Systems @@ -95,7 +95,7 @@ The plugin is built against Jenkins version **1.580** and that's the version int ### 4.0.0 and later (New!) -Ever since release 4.0.0, a command-line client or tool is no longer necessary as all the interaction with TFS or Team Services is done using the [TFS SDK for Java]. The native libraries needed by the SDK are automatically copied to a sub-directory under the agent user's home folder. +Ever since release 4.0.0, a command-line client or tool is no longer necessary as all the interaction with TFS or Azure DevOps is done using the [TFS SDK for Java]. The native libraries needed by the SDK are automatically copied to a sub-directory under the agent user's home folder. ### 3.2.0 and earlier @@ -106,7 +106,7 @@ Versions 3.2.0 and earlier of the plugin required a command line tool to be inst ## Global configuration -To make use of the Git integration with TFS/Team Services and/or to use automatic credentials configuration with the TFVC SCM, it is necessary to first configure your team project collection(s). Follow these instructions for each team project collection (most organizations will only have one). +To make use of the Git integration with TFS/Azure DevOps and/or to use automatic credentials configuration with the TFVC SCM, it is necessary to first configure your team project collection(s). Follow these instructions for each team project collection (most organizations will only have one). 1. Add credentials: 1. Select **Jenkins** > **Credentials** @@ -127,8 +127,8 @@ To make use of the Git integration with TFS/Team Services and/or to use automati 5. Click **OK** 2. Add the collection URL and associate it with the right credentials: 1. Select **Jenkins** > **Manage Jenkins** > **Configure System** - 2. Scroll to **TFS/Team Services** and click **Add** - 1. If using Team Services, the value of the _Collection URL_ field should omit `/DefaultCollection`. + 2. Scroll to **TFS/Azure DevOps** and click **Add** + 1. If using Azure DevOps, the value of the _Collection URL_ field should omit `/DefaultCollection`. 2. Select the associated `Credentials` value created earlier. 3. Click **Test Connection**. 3. Click **Save** @@ -159,17 +159,17 @@ Field | Description `Collection URL` | The URL to the [Team Project Collection](https://msdn.microsoft.com/en-us/library/dd236915(v=vs.120).aspx). If you added your team project collection(s) in the global configuration, the field will show you a list to pick from. Examples: `https://tfs02.codeplex.com`, `https://fabrikam-fiber-inc.visualstudio.com`, `http://tfs:8080/tfs/DefaultCollection` `Project path` | The Team Project and path to retrieve from the server. The project path must start with `$/`, and contain any sub path that exists in the project repository. Example: `$/Fabrikam-Fiber-TFVC/AuthSample-dev` `Credentials` | If you added your team project collection(s) in the global configuration, select **Automatic** and the credentials will be looked up automatically, otherwise you can select **Manual** and configure the `User name` and `User password` fields. -`Manual` > `User name` | The name of the user that will be connecting to TFS/Team Services to query history, checkout files, etc. See _User name and password_ below for a full description. +`Manual` > `User name` | The name of the user that will be connecting to TFS/Azure DevOps to query history, checkout files, etc. See _User name and password_ below for a full description. `Manual` > `User password` | The password, alternate password or personal access token associated with the user. See _User name and password_ below for more details. `Use update` | If this option is checked, then the workspace will not be deleted and re-created at the start of each build, making the build faster, but this causes the artifacts from the previous build to remain when a new build starts. `Local workfolder` | The name of the local work folder. The specified folder will contain the files retrieved from the repository. Default is `.`, ie the files will be downloaded into the Hudson workspace folder. `Workspace name` | The name of the workspace that Jenkins should use when creating and deleting workspaces on the server. The workspace name supports three macros; `${JOB_NAME}` is replaced by the job name, `${USER_NAME}` is replaced by the user name Jenkins is running as and `${NODE_NAME}` is replaced by the name of the node. Default workspace name is `Hudson-${JOB_NAME}-${NODE_NAME}`. `Cloaked paths` | A collection of server paths to cloak to exclude from the workspace and from the build trigger. Multiple entries must be placed onto separate lines. -`Repository browser` | Select `Microsoft Team Foundation Server/Visual Studio Team Services` to turn on links inside Jenkins jobs (in the **Changes** page) back to TFS/Team Services, for easier traceability. If the TFS server is reached by users through a different URL than that provided in `Collection URL`, such as the Fully-Qualified Domain Name (FQDN), provide a value for the `URL` sub-field. +`Repository browser` | Select `Microsoft Team Foundation Server/Azure DevOps` to turn on links inside Jenkins jobs (in the **Changes** page) back to TFS/Azure DevOps, for easier traceability. If the TFS server is reached by users through a different URL than that provided in `Collection URL`, such as the Fully-Qualified Domain Name (FQDN), provide a value for the `URL` sub-field. ### Git -If your source code is in a Git repository located on a TFS/Team Services server, this section is for you. +If your source code is in a Git repository located on a TFS/Azure DevOps server, this section is for you. > :warning: Make sure you first followed the instructions in the **Global configuration** section and added your team project collection(s), associated with credentials. :warning: @@ -177,17 +177,17 @@ If your source code is in a Git repository located on a TFS/Team Services server If you didn't have the Git plug-in for Jenkins already, installing the TFS plug-in for Jenkins should have brought it on as a dependency. -1. Use the **Git** _Source Code Management_ and add the URL to your Git repository in TFS/Team Services, omitting the `/DefaultCollection` if you are using Team Services. +1. Use the **Git** _Source Code Management_ and add the URL to your Git repository in TFS/Azure DevOps, omitting the `/DefaultCollection` if you are using Azure DevOps. 2. If you haven't done so already, follow the instructions in the "User name and password" section to generate a Personal Access Token, and then add a "Credentials" entry as specified in the "Global configuration" section. You should then be able to select it in the _Credentials_ field. -3. To be able to build the merge commits created for pull requests in TFS/Team Services, click the **Advanced...** button +3. To be able to build the merge commits created for pull requests in TFS/Azure DevOps, click the **Advanced...** button 1. In the _Name_ field, enter **origin** (or some unique name if you already have other repositories) 2. In the _Refspec_ field, enter `+refs/heads/*:refs/remotes/origin/* +refs/pull/*:refs/remotes/origin-pull/*` (replacing "origin" as necessary) -4. If you haven't already enabled the Push Trigger for all jobs, scroll down to _Build Triggers_ and you can check the **Build when a change is pushed to TFS/Team Services** checkbox. -5. If you haven't already enabled Team Status for all jobs, scroll down to _Build_, select **Add build step** > **Set build pending status in TFS/Team Services**, moving it _first_ in the list of steps, to notify TFS/Team Services as early as possible that a Jenkins build has been started. +4. If you haven't already enabled the Push Trigger for all jobs, scroll down to _Build Triggers_ and you can check the **Build when a change is pushed to TFS/Azure DevOps** checkbox. +5. If you haven't already enabled Team Status for all jobs, scroll down to _Build_, select **Add build step** > **Set build pending status in TFS/Azure DevOps**, moving it _first_ in the list of steps, to notify TFS/Azure DevOps as early as possible that a Jenkins build has been started. 6. Add other build steps, as necessary. -7. If you haven't already enabled Team Status for all jobs, scroll down to _Post-build Actions_, select **Add post-build action** > **Set build completion status in TFS/Team Services**. -8. If you would like to collect results for publication in TFS/Team Services, scroll down to _Post-build Actions_, select **Add post-build action** > **Collect results for TFS/Team Services** and then add one or more results to collect. -9. If the Jenkins job will be used to validate pull requests in TFS/Team Services and you would like to add links from the associated work items back to the Jenkins build, select **Add post-build action** > **Add link to associated work items in TFS/Team Services**. +7. If you haven't already enabled Team Status for all jobs, scroll down to _Post-build Actions_, select **Add post-build action** > **Set build completion status in TFS/Azure DevOps**. +8. If you would like to collect results for publication in TFS/Azure DevOps, scroll down to _Post-build Actions_, select **Add post-build action** > **Collect results for TFS/Azure DevOps** and then add one or more results to collect. +9. If the Jenkins job will be used to validate pull requests in TFS/Azure DevOps and you would like to add links from the associated work items back to the Jenkins build, select **Add post-build action** > **Add link to associated work items in TFS/Azure DevOps**. ### User name and password @@ -198,18 +198,18 @@ For \[on-premises\] Team Foundation Server, the _User name_ can be specified in 1. `EXAMPLE-DOMAIN\user` 2. `user@domain.example.com` -#### Visual Studio Team Services (Team Services, previously known as Visual Studio Online) +#### Azure DevOps (Azure DevOps, previously known as Visual Studio Online) -For Team Services, there are also two options: +For Azure DevOps, there are also two options: 1. Personal access tokens (recommended) - 1. In Team Services, click your name in the top right corner and select **Security**. + 1. In Azure DevOps, click your name in the top right corner and select **Security**. 2. In the _Personal access tokens_ area, select **Add**. - 3. Describe the token (use something like "Jenkins server at jenkins.example.com"), select an expiry timeframe, double-check the Team Services account the token will be valid for and, if the user account will be used for TFVC, select **All scopes** otherwise you can select smaller scopes based on what features you will need. + 3. Describe the token (use something like "Jenkins server at jenkins.example.com"), select an expiry timeframe, double-check the Azure DevOps account the token will be valid for and, if the user account will be used for TFVC, select **All scopes** otherwise you can select smaller scopes based on what features you will need. 4. Click **\[Create Token\]** and copy the generated personal access token to the clipboard. - 5. Back to Jenkins, enter the e-mail address associated with your Team Services account as the _User name_ and the generated personal access token as the _User password_. + 5. Back to Jenkins, enter the e-mail address associated with your Azure DevOps account as the _User name_ and the generated personal access token as the _User password_. 2. Alternate credentials - 1. In Team Services, click your name in the top right corner and select **Security**. + 1. In Azure DevOps, click your name in the top right corner and select **Security**. 2. In the _Alternate credentials_ area, select **Enable alternate authentication credentials**. 3. Enter a secondary user name and password, then click **\[Save\]**. 4. Back to Jenkins, re-enter those credentials in the _User name_ and _User password_ fields. @@ -244,11 +244,11 @@ In the event Jenkins is deployed on a network with no direct access to other net Follow the instructions at [JenkinsBehindProxy](https://wiki.jenkins-ci.org/display/JENKINS/JenkinsBehindProxy) to configure Jenkins' use of a proxy server, which the TFS plugin also uses. -## Integration with TFS/Team Services (New since version 5.0.0) +## Integration with TFS/Azure DevOps (New since version 5.0.0) -There are some steps to perform in both Jenkins and in TFS/Team Services to activate the integration. This section assumes you have already configured one or more team project collections, as described in the **Global configuration** section above and then configured the Jenkins jobs as described in the **Git** section above. +There are some steps to perform in both Jenkins and in TFS/Azure DevOps to activate the integration. This section assumes you have already configured one or more team project collections, as described in the **Global configuration** section above and then configured the Jenkins jobs as described in the **Git** section above. -### Trigger builds when code is pushed to a Git repository in TFS/Team Services +### Trigger builds when code is pushed to a Git repository in TFS/Azure DevOps 1. Go to the team project's _Administration_ and then select **Service Hooks** 2. Select the `+` button @@ -257,14 +257,14 @@ There are some steps to perform in both Jenkins and in TFS/Team Services to acti 5. You can now configure which action will be performed. The choices are **Trigger generic build** and **Trigger Git build**. Once you select the action, its _Settings_ must be configured 1. Enter the URL to your Jenkins server. (hint: it's the destination when you click the Jenkins logo in the top left) 2. Enter your _User name_ - 3. For the _User API token_, click on your user name in Jenkins (in the top-right corner), then **Configure** and finally click the **Show API Token...*** button. Copy & paste the token back in TFS/Team Services + 3. For the _User API token_, click on your user name in Jenkins (in the top-right corner), then **Configure** and finally click the **Show API Token...*** button. Copy & paste the token back in TFS/Azure DevOps 4. Once valid credentials have been provided, more fields will become available. If you selected **Trigger generic build**, keep going with these fields 1. The _Build_ field should now be configurable as a drop-down list 2. Add any additional parameters, if any 5. Click **Test**, inspect the results of the test and click **Close** 6. If the test was successful, click **Finish** -### Trigger builds when a pull request is created or updated in TFS/Team Services +### Trigger builds when a pull request is created or updated in TFS/Azure DevOps 1. Go to the team project's _Administration_ and then select **Service Hooks** 2. Select the `+` button @@ -273,7 +273,7 @@ There are some steps to perform in both Jenkins and in TFS/Team Services to acti 5. The only action available is **Trigger generic build**, its _Settings_ must be configured 1. Enter the URL to your Jenkins server. (hint: it's the destination when you click the Jenkins logo in the top left) 2. Enter your _User name_ - 3. For the _User API token_, click on your user name in Jenkins (in the top-right corner), then **Configure** and finally click the **Show API Token...*** button. Copy & paste the token back in TFS/Team Services + 3. For the _User API token_, click on your user name in Jenkins (in the top-right corner), then **Configure** and finally click the **Show API Token...*** button. Copy & paste the token back in TFS/Azure DevOps 4. Once valid credentials have been provided, more fields will become available 5. The _Build_ field should now be configurable as a drop-down list 6. Add any additional parameters, if any @@ -289,23 +289,23 @@ The plugin will set the following environment variables for the build, after a c * **TFS_WORKFOLDER** \- The full path to the working folder. * **TFS_PROJECTPATH** \- The TFVC project path that is mapped to the workspace. * **TFS_SERVERURL** \- The URL to the Team Project Collection. -* **TFS_USERNAME** \- The user name that is used to connect to TFS/Team Services. +* **TFS_USERNAME** \- The user name that is used to connect to TFS/Azure DevOps. * **TFS_CHANGESET** \- The change set number that is checked out in the workspace -# Trigger release in TFS/Team Services +# Trigger release in TFS/Azure DevOps [MSDN documentation](https://blogs.msdn.microsoft.com/visualstudioalm/2016/05/27/continuous-deploymentdelivery-with-jenkins-and-vs-team-services/) ### Overview Once you have configured Continuous Integration (CI) with Jenkins to be able to build with every code checkin/commit, the next step toward automating your DevOps pipeline is to be able to deploy automatically by setting up the Continuous Deployment (CD) pipeline. -[VS Team Service Release Management](https://www.visualstudio.com/features/release-management-vs) service lets you automate your deployments so that you could deliver your apps/services easily and deliver them often. You can setup the CI and CD process all on VS Team Services. However, if you have the CI pipleine already set with Jenkins, VS Team Service has good integration points through its [APIs](https://www.visualstudio.com/integrate/api/overview#Releasepreview) that can let you interact with its release service from any other third-party - Jenkins in this case. +[VS Team Service Release Management](https://www.visualstudio.com/features/release-management-vs) service lets you automate your deployments so that you could deliver your apps/services easily and deliver them often. You can setup the CI and CD process all on VS Azure DevOps. However, if you have the CI pipleine already set with Jenkins, VS Team Service has good integration points through its [APIs](https://www.visualstudio.com/integrate/api/overview#Releasepreview) that can let you interact with its release service from any other third-party - Jenkins in this case. -This plugin makes use these APIs that lets you trigger a release in VS Team Services or TFS, upon completion of a build in Jenkins. The plugin has a post build step - "VS Team Services Continuous Deployment". +This plugin makes use these APIs that lets you trigger a release in VS Azure DevOps or TFS, upon completion of a build in Jenkins. The plugin has a post build step - "VS Azure DevOps Continuous Deployment". ### Using the plugin -Assuming that you have already [created the Release Definition](https://www.visualstudio.com/en-us/docs/release/author-release-definition/more-release-definition) and [linked the Jenkins as artifact source](https://www.visualstudio.com/en-us/docs/release/author-release-definition/understanding-artifacts#jenkins) in TFS/Team Services - Release Management, you need to follow the following steps at the Jenkins side to trigger releases automatically, upon build creation. +Assuming that you have already [created the Release Definition](https://www.visualstudio.com/en-us/docs/release/author-release-definition/more-release-definition) and [linked the Jenkins as artifact source](https://www.visualstudio.com/en-us/docs/release/author-release-definition/understanding-artifacts#jenkins) in TFS/Azure DevOps - Release Management, you need to follow the following steps at the Jenkins side to trigger releases automatically, upon build creation. **0. Setup Release Definition with Jenkins as artifact source** This document assumes that you have already set up the RM definition that uses Jenkins artifact to deploy. This means your build/job is configured properly and archives artifacts. If not, see the following video to set up Release Definition with Jenkins build @@ -314,22 +314,22 @@ This document assumes that you have already set up the RM definition that uses J **1. Add the post build action** -Go to the Job configuration and add the post build action - **Trigger release in TFS/Team Services**. +Go to the Job configuration and add the post build action - **Trigger release in TFS/Azure DevOps**. ![Add post build action](images/addPostBuildAction.png) **2. Fill in the required fields** Fill in the details required for this post build action. You need the following details: * **Collection URL:** e.g. https://fabfiber.visualstudio.com/**DefaultCollection** <- Note that you need the url till the collection. -* **Team project:** The VS Team Services Project in which you have defined the release definition. +* **Team project:** The VS Azure DevOps Project in which you have defined the release definition. * **Release definition:** The Release definition **name** that links this Jenkins job as an artifact source. -You need to now enter the credentials that lets Jenkins trigger a release with the latest completed build, on your behalf. If you are using VS Team Services, you just need to enter **PAT** with atleast "Release (read, write and execute)" scope. (Refer to this [link](https://www.visualstudio.com/en-us/get-started/setup/use-personal-access-tokens-to-authenticate) to understand how to create PAT). In case you are using TFS, you need to enter the **username** and **password**. +You need to now enter the credentials that lets Jenkins trigger a release with the latest completed build, on your behalf. If you are using VS Azure DevOps, you just need to enter **PAT** with atleast "Release (read, write and execute)" scope. (Refer to this [link](https://www.visualstudio.com/en-us/get-started/setup/use-personal-access-tokens-to-authenticate) to understand how to create PAT). In case you are using TFS, you need to enter the **username** and **password**. ![Add post build action](images/fillFieldsForPostBuildAction.JPG) **3. All set. See CD in action** You have now automated your deployment trigger thereby enabling continuous deployment i.e. a checkin/commit would trigger a build and that will trigger a release. -Go ahead and test your setup by manually triggering a build in Jenkins or by a code checkin/commit that kicks off Jenkins build which in turn will trigger the release in VS Team Services. +Go ahead and test your setup by manually triggering a build in Jenkins or by a code checkin/commit that kicks off Jenkins build which in turn will trigger the release in VS Azure DevOps. # FAQ @@ -374,7 +374,7 @@ Details about previous releases can be found on the [Releases page](https://gith [CloudBees]: https://www.cloudbees.com/ [Jenkins JIRA]: http://issues.jenkins-ci.org/secure/IssueNavigator.jspa?mode=hide&reset=true&jqlQuery=project+%3D+JENKINS+AND+status+in+%28Open%2C+%22In+Progress%22%2C+Reopened%29+AND+component+%3D+%27tfs-plugin%27 [Team Foundation Version Control]: https://msdn.microsoft.com/en-us/library/ms181237.aspx -[Visual Studio Team Services]: https://www.visualstudio.com/products/visual-studio-team-services-vs +[Azure DevOps]: https://www.visualstudio.com/products/visual-studio-team-services-vs [TFS SDK for Java]: http://blogs.msdn.com/b/bharry/archive/2011/05/16/announcing-a-java-sdk-for-tfs.aspx [Microsoft Team Explorer Everywhere]: http://www.microsoft.com/en-us/download/details.aspx?id=40785 [JENKINS-4021]: https://issues.jenkins-ci.org/browse/JENKINS-4021 diff --git a/Releasing.md b/Releasing.md index 803fe2241..2ad13bb44 100644 --- a/Releasing.md +++ b/Releasing.md @@ -26,7 +26,7 @@ Pre-requisites ``` 2. A great test is to try to perform a `mvn deploy`, which should attempt to upload some files to the snapshot repo and will need those credentials. 3. If you're worried about putting your "Jenkins infrastructure" password in plain-text in that file, Maven has a password encryption facility that relies on a master password in another file. Presumably, you secure access to the 2nd file by placing it on a thumbdrive that you carry with you when you're not at your computer, etc. -1. A TFS server or a VSTS account, configured as per `Testing.md` +1. A TFS server or a Azure DevOps account, configured as per `Testing.md` Release ======= @@ -34,7 +34,7 @@ Release 1. Pre-release. Perform these manual steps on your workstation: 1. Run a full build, with all its end-to-end tests; it takes about 5 minutes: ``` - mvn clean verify --batch-mode -Dtfs_server_name=<TFS host name or VSTS account host name> -Dtfs_user_name=<user> -Dtfs_user_password=<password> + mvn clean verify --batch-mode -Dtfs_server_name=<TFS host name or Azure DevOps account host name> -Dtfs_user_name=<user> -Dtfs_user_password=<password> ``` 2. Look at the commits since the last release by going to https://github.com/jenkinsci/tfs-plugin/releases and clicking the "XX commits to master since this release" link. It will be easiest to surf the associated pull requests, so hit Ctrl+F, search for "Merge pull request" and Ctrl+click every #XXX link to the right of the highlights. 3. Fill in the categories of the `ReleaseNotes.md` template, usually in one of the following formats: @@ -64,7 +64,7 @@ Release 1. Add timestamps to the Console Output 2. Inject environment variables to the build process 1. **COMPUTERNAME** (the host name of the Jenkins node that will run the job) - 2. **TFS_SERVER_NAME** (the TFS host name or VSTS account host name) + 2. **TFS_SERVER_NAME** (the TFS host name or Azure DevOps account host name) 3. Use secret text(s) or file(s) 1. **TFS_USER_NAME** and **TFS_USER_PASSWORD** are initialized from a credential 4. Build. Add the following steps: diff --git a/Testing.md b/Testing.md index 27b686dbc..d89fec44c 100644 --- a/Testing.md +++ b/Testing.md @@ -28,7 +28,7 @@ You'll need to be (or involve) an administrator. It is assumed your TFS server 4. Confirm you can browse to the **FunctionalTests** team project. -How to configure your Team Services account +How to configure your Azure DevOps account ------------------------------------------- TODO: write this From a1e56e6f768d914aa6c3dcd9361b07237ee7fae9 Mon Sep 17 00:00:00 2001 From: keljos <31480321+keljos@users.noreply.github.com> Date: Mon, 5 Aug 2019 14:27:01 -0400 Subject: [PATCH 29/42] Minor updates to naming. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a28231b35..6755d1867 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ There are two possibilities to trigger a build with SCM Changeset: The following sub-sections list the various versions of software that were tested and are thus supported. The plugin might work with other versions, they just haven't been tested. -## Team Foundation Server (TFS) / Azure DevOps (Azure DevOps) +## Team Foundation Server (TFS) / Azure DevOps The following table indicates compatibility and support for versions of TFS and Azure DevOps. @@ -198,7 +198,7 @@ For \[on-premises\] Team Foundation Server, the _User name_ can be specified in 1. `EXAMPLE-DOMAIN\user` 2. `user@domain.example.com` -#### Azure DevOps (Azure DevOps, previously known as Visual Studio Online) +#### Azure DevOps (previously known as Visual Studio Team Services or VSTS) For Azure DevOps, there are also two options: From ccf9ea9a91196fe07bf36f8610104dad3189e545 Mon Sep 17 00:00:00 2001 From: keljos <31480321+keljos@users.noreply.github.com> Date: Mon, 12 Aug 2019 16:19:35 -0400 Subject: [PATCH 30/42] Prepare for release 5.157.0 (#227) * Prepare for release 5.157.0 * update poms --- ReleaseNotes.md | 9 +++------ pom.xml | 2 +- tfs-sdk/pom.xml | 2 +- tfs/pom.xml | 2 +- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 90f7018d0..271810061 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,8 +1,5 @@ -These notes are for release **(to be determined)**. +These notes are for release **5.157.0**. Other releases and their notes can be found at the [tfs-plugin GitHub Releases](https://github.com/jenkinsci/tfs-plugin/releases) page. - -* Major - * TODO - + * Minor: - * TODO + * Update documentation to reflect rebranding of VSTS as Azure DevOps diff --git a/pom.xml b/pom.xml index 2ddd57258..04f83c2f8 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ tfs-parent pom Team Foundation Server Plug-in parent - 5.142.0-SNAPSHOT + 5.157.0-SNAPSHOT 1.642.3 diff --git a/tfs-sdk/pom.xml b/tfs-sdk/pom.xml index 7652e5cc7..9288cc7b8 100644 --- a/tfs-sdk/pom.xml +++ b/tfs-sdk/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.142.0-SNAPSHOT + 5.157.0-SNAPSHOT tfs-sdk diff --git a/tfs/pom.xml b/tfs/pom.xml index 38b083e46..6f8e6e077 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.142.0-SNAPSHOT + 5.157.0-SNAPSHOT tfs From 73bc5fcefc9ee52af26bc72e8433469d7ba8cc32 Mon Sep 17 00:00:00 2001 From: keljos <31480321+keljos@users.noreply.github.com> Date: Mon, 12 Aug 2019 17:11:46 -0400 Subject: [PATCH 31/42] Update to reflect handle changes Includes handle changes for keljos and jpricket --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 04f83c2f8..1d643cf8a 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ -5
- jpricketmsft + jpricket Jason Prickett jpricket@microsoft.com -5 @@ -54,7 +54,7 @@ +5 - kelliejos + keljos Kellie Jos kej@microsoft.com -5 From fb0791aa250c907faa464263ddccd1c46c8cd20e Mon Sep 17 00:00:00 2001 From: Jason Prickett Date: Mon, 12 Aug 2019 21:31:11 +0000 Subject: [PATCH 32/42] [maven-release-plugin] prepare release tfs-5.157.0 --- pom.xml | 4 ++-- tfs-sdk/pom.xml | 2 +- tfs/pom.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 1d643cf8a..8cca5dbb9 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ tfs-parent pom Team Foundation Server Plug-in parent - 5.157.0-SNAPSHOT + 5.157.0 1.642.3 @@ -69,7 +69,7 @@ scm:git:https://github.com/jenkinsci/tfs-plugin.git scm:git:https://github.com/jenkinsci/tfs-plugin.git - HEAD + tfs-5.157.0 diff --git a/tfs-sdk/pom.xml b/tfs-sdk/pom.xml index 9288cc7b8..f009b1ac8 100644 --- a/tfs-sdk/pom.xml +++ b/tfs-sdk/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.157.0-SNAPSHOT + 5.157.0 tfs-sdk diff --git a/tfs/pom.xml b/tfs/pom.xml index 6f8e6e077..fa5b5483a 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -3,13 +3,13 @@ org.jenkins-ci.plugins tfs-parent - 5.157.0-SNAPSHOT + 5.157.0 tfs hpi Team Foundation Server Plug-in - 5.142.0-SNAPSHOT + 5.157.0 http://wiki.jenkins-ci.org/display/JENKINS/Team+Foundation+Server+Plugin From fccd8b66a46f124764e37569e36d46aba3c4a20e Mon Sep 17 00:00:00 2001 From: Jason Prickett Date: Mon, 12 Aug 2019 21:31:17 +0000 Subject: [PATCH 33/42] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- tfs-sdk/pom.xml | 2 +- tfs/pom.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 8cca5dbb9..1d643cf8a 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ tfs-parent pom Team Foundation Server Plug-in parent - 5.157.0 + 5.157.0-SNAPSHOT 1.642.3 @@ -69,7 +69,7 @@ scm:git:https://github.com/jenkinsci/tfs-plugin.git scm:git:https://github.com/jenkinsci/tfs-plugin.git - tfs-5.157.0 + HEAD diff --git a/tfs-sdk/pom.xml b/tfs-sdk/pom.xml index f009b1ac8..9288cc7b8 100644 --- a/tfs-sdk/pom.xml +++ b/tfs-sdk/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.157.0 + 5.157.0-SNAPSHOT tfs-sdk diff --git a/tfs/pom.xml b/tfs/pom.xml index fa5b5483a..504eec898 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -3,13 +3,13 @@ org.jenkins-ci.plugins tfs-parent - 5.157.0 + 5.157.0-SNAPSHOT tfs hpi Team Foundation Server Plug-in - 5.157.0 + 5.157.0-SNAPSHOT http://wiki.jenkins-ci.org/display/JENKINS/Team+Foundation+Server+Plugin From bb3fb4e401f2a6b234a98f7f16cb1a0996d113cb Mon Sep 17 00:00:00 2001 From: keljos <31480321+keljos@users.noreply.github.com> Date: Tue, 13 Aug 2019 10:40:22 -0400 Subject: [PATCH 34/42] Clear out Release Notes for next release. --- ReleaseNotes.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 271810061..c8a06eb51 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,5 +1,9 @@ -These notes are for release **5.157.0**. +These notes are for release **(to be determined)**. Other releases and their notes can be found at the [tfs-plugin GitHub Releases](https://github.com/jenkinsci/tfs-plugin/releases) page. - + +* Major + * TODO + * Minor: - * Update documentation to reflect rebranding of VSTS as Azure DevOps + * TODO + From 913fb8ad8cfeb8c6e3f11847ec092ab04f08546c Mon Sep 17 00:00:00 2001 From: Kellie Jos <31480321+keljos@users.noreply.github.com> Date: Wed, 8 Jan 2020 15:08:55 -0500 Subject: [PATCH 35/42] Add leantk as contributor (#229) --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 1d643cf8a..db4267157 100644 --- a/pom.xml +++ b/pom.xml @@ -65,6 +65,12 @@ manfred@simpligility.com -8 + + leantk + Leah Antkiewicz + leantk@microsoft.com + -5 + scm:git:https://github.com/jenkinsci/tfs-plugin.git From 009fe59002b02115dd509aac7d634dabd4f846a2 Mon Sep 17 00:00:00 2001 From: Kalyan Date: Sat, 15 Feb 2020 18:12:56 +0530 Subject: [PATCH 36/42] Using full name of the project to make sure rm webhook trigger works for projects under the folder (#231) --- .../hudson/plugins/tfs/rm/ConnectReleaseWebHookEvent.java | 2 +- .../main/java/hudson/plugins/tfs/rm/ReleaseManagementCI.java | 2 +- .../main/java/hudson/plugins/tfs/rm/ReleaseWebHookAction.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tfs/src/main/java/hudson/plugins/tfs/rm/ConnectReleaseWebHookEvent.java b/tfs/src/main/java/hudson/plugins/tfs/rm/ConnectReleaseWebHookEvent.java index 04875fcec..f4d003ac3 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/rm/ConnectReleaseWebHookEvent.java +++ b/tfs/src/main/java/hudson/plugins/tfs/rm/ConnectReleaseWebHookEvent.java @@ -107,7 +107,7 @@ private AbstractProject validateAndGetJenkinsProject(final ReleaseWebHookResourc } for (final Item project : Jenkins.getActiveInstance().getAllItems()) { - if (project instanceof AbstractProject && project.getName().equalsIgnoreCase(projectName)) { + if (project instanceof AbstractProject && project.getFullName().equalsIgnoreCase(projectName)) { return (AbstractProject) project; } } diff --git a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseManagementCI.java b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseManagementCI.java index 2e1a10893..070e0f5af 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseManagementCI.java +++ b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseManagementCI.java @@ -300,7 +300,7 @@ public boolean isApplicable(Class jobType) @Override public String getDisplayName() { - return "Trigger release in TFS/Team Services"; + return "Trigger release in Azure Devops Server/Services"; } public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Item owner, diff --git a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookAction.java b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookAction.java index 64b74931a..f1d575acb 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookAction.java +++ b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookAction.java @@ -70,7 +70,7 @@ public boolean perform(final AbstractBuild build, final Launcher launcher, json = JSONObject.fromObject(payload); } - json.put("name", build.getProject().getName()); + json.put("name", build.getProject().getFullName()); json.put("id", build.getNumber()); json.put("startedBy", getStartedBy(build)); @@ -144,7 +144,7 @@ public boolean isApplicable(final Class jobType) { @Override public String getDisplayName() { - return "TFS/Team Services Release Webhook"; + return "Azure Devops Server/Services Release Webhook"; } } From f1387fc7578c512d020302b6908ae273d0ff90ee Mon Sep 17 00:00:00 2001 From: Kalyan Date: Sun, 16 Feb 2020 02:36:39 +0530 Subject: [PATCH 37/42] Encoding payload string to UTF-8 before publishing the evennt. (#232) The payload checksum was calculated after converting to UTF-8 string. If we don't convert the payload as well, the checksum won't match. --- .../main/java/hudson/plugins/tfs/rm/ReleaseWebHookAction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookAction.java b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookAction.java index f1d575acb..5e27a037b 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookAction.java +++ b/tfs/src/main/java/hudson/plugins/tfs/rm/ReleaseWebHookAction.java @@ -113,7 +113,7 @@ private ReleaseWebHookStatus sendJobCompletedEvent(final JSONObject json, final request.addHeader("X-Jenkins-Signature", signature); } - request.setEntity(new StringEntity(payload)); + request.setEntity(new StringEntity(new String(payload.getBytes("UTF-8")))); final HttpResponse response = client.execute(request); final int statusCode = response.getStatusLine().getStatusCode(); From bbae1e5cdfd03371f93ad98bddad3d97b2cd7972 Mon Sep 17 00:00:00 2001 From: Kalyan Date: Thu, 20 Feb 2020 09:21:02 +0530 Subject: [PATCH 38/42] Preparing for release 5.157.1 (#234) --- ReleaseNotes.md | 8 +++----- pom.xml | 2 +- tfs-sdk/pom.xml | 2 +- tfs/pom.xml | 4 ++-- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index c8a06eb51..13bd5fdb8 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,9 +1,7 @@ -These notes are for release **(to be determined)**. +These notes are for release **5.157.1**. Other releases and their notes can be found at the [tfs-plugin GitHub Releases](https://github.com/jenkinsci/tfs-plugin/releases) page. -* Major - * TODO - * Minor: - * TODO + * BugFix: There was an issue in enabling RM/Pipeline webhook for projects under a folder. Now using full name of the project to ensure webhook works. + * BugFix: Encoding the event payload to UTF-8 before publishing the RM/Pipeline webhook event. This is because the payload checksum is calculated after converting the payload to UTF-8. diff --git a/pom.xml b/pom.xml index db4267157..1e631c4b2 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ tfs-parent pom Team Foundation Server Plug-in parent - 5.157.0-SNAPSHOT + 5.157.1-SNAPSHOT 1.642.3 diff --git a/tfs-sdk/pom.xml b/tfs-sdk/pom.xml index 9288cc7b8..44b6fda27 100644 --- a/tfs-sdk/pom.xml +++ b/tfs-sdk/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.157.0-SNAPSHOT + 5.157.1-SNAPSHOT tfs-sdk diff --git a/tfs/pom.xml b/tfs/pom.xml index 504eec898..6dc87f969 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -3,13 +3,13 @@ org.jenkins-ci.plugins tfs-parent - 5.157.0-SNAPSHOT + 5.157.1-SNAPSHOT tfs hpi Team Foundation Server Plug-in - 5.157.0-SNAPSHOT + 5.157.1-SNAPSHOT http://wiki.jenkins-ci.org/display/JENKINS/Team+Foundation+Server+Plugin From 0ebfc2acee98227d4938fd2fc4764ac4a840e12c Mon Sep 17 00:00:00 2001 From: Jason Prickett Date: Thu, 20 Feb 2020 04:02:15 +0000 Subject: [PATCH 39/42] [maven-release-plugin] prepare release tfs-5.157.1 --- pom.xml | 4 ++-- tfs-sdk/pom.xml | 2 +- tfs/pom.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 1e631c4b2..0614fae68 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ tfs-parent pom Team Foundation Server Plug-in parent - 5.157.1-SNAPSHOT + 5.157.1 1.642.3 @@ -75,7 +75,7 @@ scm:git:https://github.com/jenkinsci/tfs-plugin.git scm:git:https://github.com/jenkinsci/tfs-plugin.git - HEAD + tfs-5.157.1 diff --git a/tfs-sdk/pom.xml b/tfs-sdk/pom.xml index 44b6fda27..7f7e23cde 100644 --- a/tfs-sdk/pom.xml +++ b/tfs-sdk/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.157.1-SNAPSHOT + 5.157.1 tfs-sdk diff --git a/tfs/pom.xml b/tfs/pom.xml index 6dc87f969..ddc5735a3 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -3,13 +3,13 @@ org.jenkins-ci.plugins tfs-parent - 5.157.1-SNAPSHOT + 5.157.1 tfs hpi Team Foundation Server Plug-in - 5.157.1-SNAPSHOT + 5.157.1 http://wiki.jenkins-ci.org/display/JENKINS/Team+Foundation+Server+Plugin From b616cd7cfd75b757934ff64dca24544b04b023e9 Mon Sep 17 00:00:00 2001 From: Jason Prickett Date: Thu, 20 Feb 2020 04:02:21 +0000 Subject: [PATCH 40/42] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- tfs-sdk/pom.xml | 2 +- tfs/pom.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 0614fae68..1e631c4b2 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ tfs-parent pom Team Foundation Server Plug-in parent - 5.157.1 + 5.157.1-SNAPSHOT 1.642.3 @@ -75,7 +75,7 @@ scm:git:https://github.com/jenkinsci/tfs-plugin.git scm:git:https://github.com/jenkinsci/tfs-plugin.git - tfs-5.157.1 + HEAD diff --git a/tfs-sdk/pom.xml b/tfs-sdk/pom.xml index 7f7e23cde..44b6fda27 100644 --- a/tfs-sdk/pom.xml +++ b/tfs-sdk/pom.xml @@ -3,7 +3,7 @@ org.jenkins-ci.plugins tfs-parent - 5.157.1 + 5.157.1-SNAPSHOT tfs-sdk diff --git a/tfs/pom.xml b/tfs/pom.xml index ddc5735a3..6dc87f969 100644 --- a/tfs/pom.xml +++ b/tfs/pom.xml @@ -3,13 +3,13 @@ org.jenkins-ci.plugins tfs-parent - 5.157.1 + 5.157.1-SNAPSHOT tfs hpi Team Foundation Server Plug-in - 5.157.1 + 5.157.1-SNAPSHOT http://wiki.jenkins-ci.org/display/JENKINS/Team+Foundation+Server+Plugin From cdc4977ffc49de299bce5c66b6c5d6f8fc0cc87a Mon Sep 17 00:00:00 2001 From: Kalyan Date: Fri, 21 Feb 2020 10:58:09 +0530 Subject: [PATCH 41/42] Clear out Release Notes for next release. (#235) --- ReleaseNotes.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 13bd5fdb8..0128ad6de 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,7 +1,9 @@ -These notes are for release **5.157.1**. +These notes are for release **(to be determined)**. Other releases and their notes can be found at the [tfs-plugin GitHub Releases](https://github.com/jenkinsci/tfs-plugin/releases) page. - + +* Major: + * TODO + * Minor: - * BugFix: There was an issue in enabling RM/Pipeline webhook for projects under a folder. Now using full name of the project to ensure webhook works. - * BugFix: Encoding the event payload to UTF-8 before publishing the RM/Pipeline webhook event. This is because the payload checksum is calculated after converting the payload to UTF-8. + * TODO From aea4ce0f1fd82668b1ead7e2aade2ffe36a32a10 Mon Sep 17 00:00:00 2001 From: Allan Burdajewicz Date: Wed, 10 Jun 2020 01:25:36 +1000 Subject: [PATCH 42/42] [JENKINS-50331] Remove use of reflection (#230) --- .../java/hudson/plugins/tfs/model/Server.java | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/tfs/src/main/java/hudson/plugins/tfs/model/Server.java b/tfs/src/main/java/hudson/plugins/tfs/model/Server.java index 1e75c9a98..39a5e945b 100644 --- a/tfs/src/main/java/hudson/plugins/tfs/model/Server.java +++ b/tfs/src/main/java/hudson/plugins/tfs/model/Server.java @@ -2,7 +2,6 @@ package hudson.plugins.tfs.model; import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; -import com.microsoft.tfs.core.TFSConfigurationServer; import com.microsoft.tfs.core.TFSTeamProjectCollection; import com.microsoft.tfs.core.clients.versioncontrol.VersionControlClient; import com.microsoft.tfs.core.clients.webservices.IIdentityManagementService; @@ -31,7 +30,6 @@ import jenkins.security.MasterToSlaveCallable; import java.io.IOException; -import java.lang.reflect.Field; import java.net.URI; import java.util.HashMap; import java.util.Map; @@ -271,21 +269,8 @@ public synchronized void close() { this.mockableVcc.close(); } if (this.tpc != null) { - // Close the configuration server connection that should be closed by - // TFSTeamProjectCollection - // The field is private, so use reflection - // This should be removed when the TFS SDK is fixed - // Post in MSDN forum: social.msdn.microsoft.com/Forums/vstudio/en-US/79985ef1-b35d-4fc5-af0b-b95e28402b83 - try { - Field f = TFSTeamProjectCollection.class.getDeclaredField("configurationServer"); - f.setAccessible(true); - TFSConfigurationServer configurationServer = (TFSConfigurationServer) f.get(this.tpc); - if (configurationServer != null) { - configurationServer.close(); - } - f.setAccessible(false); - } catch (NoSuchFieldException ignore) { - } catch (IllegalAccessException ignore) { + if(this.tpc.getConfigurationServer() != null) { + this.tpc.getConfigurationServer().close(); } this.tpc.close(); }