package hudson.plugins.tfs.model; import com.microsoft.tfs.core.clients.versioncontrol.specs.version.LatestVersionSpec; import hudson.model.User; import hudson.plugins.tfs.TeamPluginGlobalConfig; import hudson.plugins.tfs.commands.GetFilesToWorkFolderCommand; import hudson.plugins.tfs.commands.RemoteChangesetVersionCommand; import hudson.plugins.tfs.model.ChangeSet.Item; import java.io.IOException; import java.io.Reader; import java.text.ParseException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.List; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Change; import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Changeset; import com.microsoft.tfs.core.clients.versioncontrol.soapextensions.RecursionType; import com.microsoft.tfs.core.clients.versioncontrol.specs.LabelSpec; import com.microsoft.tfs.core.clients.versioncontrol.specs.version.ChangesetVersionSpec; import com.microsoft.tfs.core.clients.versioncontrol.specs.version.DateVersionSpec; import com.microsoft.tfs.core.clients.versioncontrol.specs.version.LabelVersionSpec; import com.microsoft.tfs.core.clients.versioncontrol.specs.version.VersionSpec; import com.microsoft.tfs.core.clients.webservices.IIdentityManagementService; public class Project { private final String projectPath; private final Server server; private UserLookup userLookup; public Project(Server server, String projectPath) { this.server = server; this.projectPath = projectPath; } public String getProjectPath() { return projectPath; } static hudson.plugins.tfs.model.ChangeSet.Item convertServerChange (com.microsoft.tfs.core.clients.versioncontrol.soapextensions.Change serverChange) { final String path = serverChange.getItem().getServerItem(); final String action = serverChange.getChangeType().toUIString(true); final Item result = new Item(path, action); return result; } public static hudson.plugins.tfs.model.ChangeSet convertServerChangeset (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 User authorUser = userLookup.find(author); final String comment = serverChangeset.getComment(); final ChangeSet result = new ChangeSet(version, date, authorUser, comment); final Change[] serverChanges = serverChangeset.getChanges(); for (final Change serverChange : serverChanges) { final Item item = convertServerChange(serverChange); result.add(item); } return result; } /** * Returns a list of changes using TFS Java SDK * @param fromVersion the version to get the history from * @param toVersion the version to get the history to * @param includeFileDetails whether or not to include details of modified items * @param maxCount the maximum number of changes to return (pass Integer.MAX_VALUE for all available values). * {@literal Must be > 0.} * @return a list of change sets */ public List<ChangeSet> getVCCHistory(VersionSpec fromVersion, VersionSpec toVersion, boolean includeFileDetails, int maxCount) { final UserLookup userLookup = getOrCreateUserLookup(); final MockableVersionControlClient vcc = server.getVersionControlClient(); final Changeset[] serverChangesets = vcc.queryHistory( projectPath, fromVersion != null ? fromVersion : toVersion, 0 /* deletionId */, RecursionType.FULL, null /* user */, fromVersion, toVersion, maxCount, includeFileDetails /* includeFileDetails */, true /* slotMode */, false /* includeDownloadInfo */, false /* sortAscending */ ); final List<ChangeSet> result = new ArrayList<ChangeSet>(); if (serverChangesets != null) { for (final Changeset serverChangeset : serverChangesets) { final ChangeSet changeSet = convertServerChangeset(serverChangeset, userLookup); result.add(changeSet); } } return result; } public UserLookup getOrCreateUserLookup() { if (userLookup == null) { synchronized (this) { if (userLookup == null) { final IIdentityManagementService ims = server.createIdentityManagementService(); final TeamPluginGlobalConfig teamPluginGlobalConfig = TeamPluginGlobalConfig.get(); final UserAccountMapper mapper = teamPluginGlobalConfig.getUserAccountMapper(); userLookup = new TfsUserLookup(ims, mapper); } } } return userLookup; } /** * Returns a list of change sets containing modified items. * @param fromTimestamp the timestamp to get history from * @param toTimestamp the timestamp to get history to * @return a list of change sets */ public List<ChangeSet> getDetailedHistory(Calendar fromTimestamp, Calendar toTimestamp) { final DateVersionSpec fromVersion = new DateVersionSpec(fromTimestamp); final DateVersionSpec toVersion = new DateVersionSpec(toTimestamp); return getVCCHistory(fromVersion, toVersion, true, Integer.MAX_VALUE); } public List<ChangeSet> getDetailedHistory(final String singleVersionSpec) { final VersionSpec toVersion = VersionSpec.parseSingleVersionFromSpec(singleVersionSpec, null); return getVCCHistory(toVersion, toVersion, true, 1); } /** * Returns a list of change sets not containing the modified items. * @param fromTimestamp the timestamp to get history from * @param toTimestamp the timestamp to get history to * @return a list of change sets */ public List<ChangeSet> getBriefHistory(Calendar fromTimestamp, Calendar toTimestamp) { final DateVersionSpec fromVersion = new DateVersionSpec(fromTimestamp); final DateVersionSpec toVersion = new DateVersionSpec(toTimestamp); return getVCCHistory(fromVersion, toVersion, false, Integer.MAX_VALUE); } /** * Returns a list of change sets not containing the modified items. * @param fromChangeset the changeset number to get history from * @param toTimestamp the timestamp to get history to * @return a list of change sets */ public List<ChangeSet> getBriefHistory(int fromChangeset, Calendar toTimestamp) { final ChangesetVersionSpec fromVersion = new ChangesetVersionSpec(fromChangeset); final VersionSpec toVersion = new DateVersionSpec(toTimestamp); return getVCCHistory(fromVersion, toVersion, false, Integer.MAX_VALUE); } /** * Returns the latest changeset at the project's path. * @return the {@link ChangeSet} instance representing the last entry in the history for the path */ public ChangeSet getLatestChangeset() { final List<ChangeSet> changeSets = getVCCHistory(LatestVersionSpec.INSTANCE, null, false, 1); final ChangeSet result = changeSets.size() > 0 ? changeSets.get(0) : null; return result; } /** * 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 * @return the {@link ChangeSet} instance representing the last entry in the history for the path */ public ChangeSet getLatestUncloakedChangeset(final int fromChangeset, final Collection<String> cloakedPaths) { final ChangesetVersionSpec fromVersion = new ChangesetVersionSpec(fromChangeset); final List<ChangeSet> changeSets = getVCCHistory(fromVersion, LatestVersionSpec.INSTANCE, true, Integer.MAX_VALUE); final ChangeSet result = findLatestUncloakedChangeset(cloakedPaths, changeSets); return result; } static ChangeSet findLatestUncloakedChangeset(final Collection<String> cloakedPaths, final List<ChangeSet> changeSets) { ChangeSet result = null; // We need to search from latest to earliest, otherwise an incorrect result is produced int lastChangeSetNumber = Integer.MAX_VALUE; for (final ChangeSet s : changeSets) { final String stringVersion = s.getVersion(); final int changeSetNumber = Integer.parseInt(stringVersion, 10); if (changeSetNumber >= lastChangeSetNumber) { throw new IllegalArgumentException("The changeset numbers must be strictly decreasing."); } lastChangeSetNumber = changeSetNumber; final Collection<String> changes = s.getAffectedPaths(); final boolean fullyCloaked = isChangesetFullyCloaked(changes, cloakedPaths); if (!fullyCloaked) { result = s; break; } } return result; } /** * Returns a list of changesets without any changesets that are in cloaked paths * @param fromTimestamp the timestamp to get history from * @param toTimestamp the timestamp to get history to * @param cloakedPaths the list of "cloaked" paths that would exclude * changesets that are fully covered by one or more of these paths * @return a list of change sets */ public List<ChangeSet> getDetailedHistoryWithoutCloakedPaths(final Calendar fromTimestamp, final Calendar toTimestamp, final Collection<String> cloakedPaths) { final DateVersionSpec fromVersion = new DateVersionSpec(fromTimestamp); final DateVersionSpec toVersion = new DateVersionSpec(toTimestamp); final List<ChangeSet> changeSets = getVCCHistory(fromVersion, toVersion, true, Integer.MAX_VALUE); final ArrayList<ChangeSet> changeSetNoCloaked = new ArrayList<ChangeSet>(); for (final ChangeSet changeset : changeSets) { final Collection<String> affectedPaths = changeset.getAffectedPaths(); final boolean fullyCloaked = isChangesetFullyCloaked(affectedPaths, cloakedPaths); if (!fullyCloaked) { changeSetNoCloaked.add(changeset); } } return changeSetNoCloaked; } static boolean isChangesetFullyCloaked(final Collection<String> changesetPaths, final Collection<String> cloakedPaths) { if (cloakedPaths == null) { return false; } for (final String tfsPath : changesetPaths) { boolean isPathCloaked = false; for (final String cloakedPath : cloakedPaths) { if (tfsPath.regionMatches(true, 0, cloakedPath, 0, cloakedPath.length())) { isPathCloaked = true; break; } } if (!isPathCloaked) { return false; } } return true; } /** * Gets all files from server. * @param localPath the local path to get all files into * @param versionSpec the version spec to use when getting the files */ public void getFiles(String localPath, String versionSpec) { GetFilesToWorkFolderCommand command = new GetFilesToWorkFolderCommand(server, localPath, versionSpec); server.execute(command.getCallable()); } /** * Gets remote changeset version for specified remote path, as of versionSpec. * * @param remotePath for which to get latest changeset version * @param versionSpec a version specification to convert to a changeset number * @return changeset version for specified remote path */ public int getRemoteChangesetVersion(final String remotePath, final VersionSpec versionSpec) { RemoteChangesetVersionCommand command = new RemoteChangesetVersionCommand(server, remotePath, versionSpec); return extractChangesetNumber(command); } int extractChangesetNumber(final RemoteChangesetVersionCommand command) { final Integer changeSet = server.execute(command.getCallable()); final int result = changeSet; return result; } /** * Gets remote changeset version for the project's remote path, as of versionSpec. * * @param versionSpec a version specification to convert to a changeset number * @return changeset version for the project's remote path */ public int getRemoteChangesetVersion(final VersionSpec versionSpec) { return getRemoteChangesetVersion(projectPath, versionSpec); } @Override public int hashCode() { return new HashCodeBuilder(13, 27).append(projectPath).toHashCode(); } @Override public boolean equals(Object obj) { if (this == obj) return true; if ((obj == null) || (getClass() != obj.getClass())) return false; final Project other = (Project) obj; EqualsBuilder builder = new EqualsBuilder(); builder.append(this.projectPath, other.projectPath); return builder.isEquals(); } protected UserLookup getUserLookup() { return userLookup; } protected void setUserLookup(final UserLookup userLookup) { this.userLookup = userLookup; } }