| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| package org.apache.tools.ant.taskdefs.optional.starteam; |
| |
| import com.starbase.starteam.Folder; |
| import com.starbase.starteam.Item; |
| import com.starbase.starteam.Status; |
| import com.starbase.starteam.View; |
| import com.starbase.starteam.ViewConfiguration; |
| import java.io.IOException; |
| import java.io.File; |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Project; |
| |
| |
| /** |
| * Checks out files from a StarTeam project. |
| * It also creates all working directories on the |
| * local directory if appropriate. Ant Usage: |
| * <pre> |
| * <taskdef name="starteamcheckout" |
| * classname="org.apache.tools.ant.taskdefs.StarTeamCheckout"/> |
| * <starteamcheckout username="BuildMaster" password="ant" starteamFolder="Source" |
| * starteamurl="servername:portnum/project/view" |
| * createworkingdirectories="true"/> |
| * </pre> |
| * |
| * @version 1.1 |
| * @see <a href="http://www.borland.com/us/products/starteam/index.html" |
| * >borland StarTeam Web Site</a> |
| * |
| * @ant.task name="stcheckout" category="scm" |
| */ |
| public class StarTeamCheckout extends TreeBasedTask { |
| |
| /** |
| * holder for the createDirs attribute |
| */ |
| private boolean createDirs = true; |
| |
| /** |
| * holder for the deleteUncontrolled attribute. If true, |
| * all local files not in StarTeam will be deleted. |
| */ |
| private boolean deleteUncontrolled = true; |
| |
| /** |
| * holder for the deleteUncontrolled attribute. If true, |
| * (default) local non-binary files will be checked out using the local |
| * platform's EOL convention. If false, checkouts will preserve the |
| * server's EOL convention. |
| */ |
| private boolean convertEOL = true; |
| |
| |
| /** |
| * flag (defaults to true) to create all directories |
| * that are in the Starteam repository even if they are empty. |
| * |
| * @param value the value to set the attribute to. |
| */ |
| public void setCreateWorkingDirs(boolean value) { |
| this.createDirs = value; |
| } |
| |
| /** |
| * Whether or not all local files <i>not<i> in StarTeam should be deleted. |
| * Optional, defaults to <code>true</code>. |
| * @param value the value to set the attribute to. |
| */ |
| public void setDeleteUncontrolled(boolean value) { |
| this.deleteUncontrolled = value; |
| } |
| |
| /** |
| * Set whether or not files should be checked out using the |
| * local machine's EOL convention. |
| * Optional, defaults to <code>true</code>. |
| * @param value the value to set the attribute to. |
| */ |
| public void setConvertEOL(boolean value) { |
| this.convertEOL = value; |
| } |
| |
| /** |
| * Sets the label StarTeam is to use for checkout; defaults to the most recent file. |
| * The label must exist in starteam or an exception will be thrown. |
| * @param label the label to be used |
| */ |
| public void setLabel(String label) { |
| _setLabel(label); |
| } |
| |
| /** |
| * This attribute tells whether to do a locked checkout, an unlocked |
| * checkout or to leave the checkout status alone (default). A locked |
| * checkout locks all other users out from making changes. An unlocked |
| * checkout reverts all local files to their previous repository status |
| * and removes the lock. |
| * @see #setLocked(boolean) |
| * @see #setUnlocked(boolean) |
| */ |
| private int lockStatus = Item.LockType.UNCHANGED; |
| |
| /** |
| * Set to do a locked checkout; optional default is false. |
| * @param v True to do a locked checkout, false to checkout without |
| * changing status/. |
| * @exception BuildException if both locked and unlocked are set true |
| */ |
| public void setLocked(boolean v) throws BuildException { |
| setLockStatus(v, Item.LockType.EXCLUSIVE); |
| } |
| |
| |
| /** |
| * Set to do an unlocked checkout. Default is false; |
| * @param v True to do an unlocked checkout, false to checkout without |
| * changing status. |
| * @exception BuildException if both locked and unlocked are set true |
| */ |
| public void setUnlocked(boolean v) throws BuildException { |
| setLockStatus(v, Item.LockType.UNLOCKED); |
| } |
| |
| private void setLockStatus(boolean v, int newStatus) |
| throws BuildException { |
| if (v) { |
| if (this.lockStatus == Item.LockType.UNCHANGED) { |
| this.lockStatus = newStatus; |
| } else if (this.lockStatus != newStatus) { |
| throw new BuildException( |
| "Error: cannot set locked and unlocked both true."); |
| } |
| } |
| } |
| |
| /** |
| * should checked out files get the timestamp from the repository |
| * or the time they are checked out. True means use the repository |
| * timestamp. |
| */ |
| private boolean useRepositoryTimeStamp = false; |
| |
| /** |
| * sets the useRepositoryTimestmp member. |
| * |
| * @param useRepositoryTimeStamp |
| * true means checked out files will get the repository timestamp. |
| * false means the checked out files will be timestamped at the time |
| * of checkout. |
| */ |
| public void setUseRepositoryTimeStamp(boolean useRepositoryTimeStamp) { |
| this.useRepositoryTimeStamp = useRepositoryTimeStamp; |
| } |
| |
| /** |
| * returns the value of the useRepositoryTimestamp member |
| * |
| * @return the value of the useRepositoryTimestamp member |
| */ |
| public boolean getUseRepositoryTimeStamp() { |
| return this.useRepositoryTimeStamp; |
| } |
| |
| /** |
| * List files, dates, and statuses as of this date; optional. |
| * If not specified, the most recent version of each file will be listed. |
| * |
| * @param asOfDateParam the date as of which the listing to be made |
| * @since Ant 1.6 |
| */ |
| public void setAsOfDate(String asOfDateParam) { |
| _setAsOfDate(asOfDateParam); |
| } |
| |
| /** |
| * Date Format with which asOfDate parameter to be parsed; optional. |
| * Must be a SimpleDateFormat compatible string. |
| * If not specified, and asOfDateParam is specified, parse will use ISO8601 |
| * datetime and date formats. |
| * |
| * @param asOfDateFormat the SimpleDateFormat-compatible format string |
| * @since Ant 1.6 |
| */ |
| public void setAsOfDateFormat(String asOfDateFormat) { |
| _setAsOfDateFormat(asOfDateFormat); |
| } |
| |
| /** |
| * Override of base-class abstract function creates an |
| * appropriately configured view for checkouts - either |
| * the current view or a view from this.label or the raw |
| * view itself in the case of a revision label. |
| * |
| * @param raw the unconfigured <code>View</code> |
| * |
| * @return the snapshot <code>View</code> appropriately configured. |
| * @exception BuildException on error |
| */ |
| protected View createSnapshotView(View raw) throws BuildException { |
| |
| int labelID = getLabelID(raw); |
| |
| // if a label has been supplied and it is a view label, use it |
| // to configure the view |
| if (this.isUsingViewLabel()) { |
| return new View(raw, ViewConfiguration.createFromLabel(labelID)); |
| // if a label has been supplied and it is a revision label, use the raw |
| // the view as the snapshot |
| } else if (this.isUsingRevisionLabel()) { |
| return raw; |
| } |
| // if a date has been supplied use a view configured to the date. |
| View view = getViewConfiguredByDate(raw); |
| if (view != null) { |
| return view; |
| // otherwise, use this view configured as the tip. |
| } else { |
| return new View(raw, ViewConfiguration.createTip()); |
| } |
| } |
| |
| |
| /** |
| * Implements base-class abstract function to define tests for |
| * any preconditons required by the task. |
| * |
| * @exception BuildException thrown if both rootLocalFolder |
| * and viewRootLocalFolder are defined |
| */ |
| protected void testPreconditions() throws BuildException { |
| if (this.isUsingRevisionLabel() && this.createDirs) { |
| log("Ignoring createworkingdirs while using a revision label." |
| + " Folders will be created only as needed.", |
| Project.MSG_WARN); |
| this.createDirs = false; |
| } |
| if (lockStatus != Item.LockType.UNCHANGED) { |
| boolean lockStatusBad = false; |
| if (null != getLabel()) { |
| log("Neither locked nor unlocked may be true" |
| + " when checking out a labeled version.", |
| Project.MSG_ERR); |
| lockStatusBad = true; |
| } else if (null != getAsOfDate()) { |
| log("Neither locked nor unlocked may be true" |
| + " when checking out by date.", |
| Project.MSG_ERR); |
| lockStatusBad = true; |
| } |
| if (lockStatusBad) { |
| throw new BuildException( |
| "Lock status may not be changed" |
| + " when checking out a non-current version."); |
| } |
| } |
| if (null != getLabel() && null != getAsOfDate()) { |
| throw new BuildException( |
| "Both label and asOfDate specified. " |
| + "Unable to process request."); |
| } |
| |
| } |
| |
| /** |
| * extenders should emit to the log an entry describing the parameters |
| * that will be used by this operation. |
| * |
| * @param starteamrootFolder |
| * root folder in StarTeam for the operation |
| * @param targetrootFolder |
| * root local folder for the operation (whether specified |
| * by the user or not. |
| */ |
| |
| protected void logOperationDescription( |
| Folder starteamrootFolder, java.io.File targetrootFolder) { |
| log((this.isRecursive() ? "Recursive" : "Non-recursive") |
| + " Checkout from: " + starteamrootFolder.getFolderHierarchy()); |
| |
| log(" Checking out to" |
| + (null == getRootLocalFolder() ? "(default): " : ": ") |
| + targetrootFolder.getAbsolutePath()); |
| |
| |
| logLabel(); |
| logAsOfDate(); |
| logIncludes(); |
| logExcludes(); |
| |
| if (this.lockStatus == Item.LockType.EXCLUSIVE) { |
| log(" Items will be checked out with Exclusive locks."); |
| } else if (this.lockStatus == Item.LockType.UNLOCKED) { |
| log(" Items will be checked out unlocked " |
| + "(even if presently locked)."); |
| } else { |
| log(" Items will be checked out with no change in lock status."); |
| } |
| log(" Items will be checked out with " |
| + (this.useRepositoryTimeStamp ? "repository timestamps." |
| : "the current timestamp.")); |
| log(" Items will be checked out " |
| + (this.isForced() ? "regardless of" : "in accordance with") |
| + " repository status."); |
| if (this.deleteUncontrolled) { |
| log(" Local items not found in the repository will be deleted."); |
| } |
| log(" Items will be checked out " |
| + (this.convertEOL ? "using the local machine's EOL convention" |
| : "without changing the EOL convention used on the server")); |
| log(" Directories will be created" |
| + (this.createDirs ? " wherever they exist in the repository, even if empty." |
| : " only where needed to check out files.")); |
| |
| } |
| |
| |
| /** |
| * Implements base-class abstract function to perform the checkout |
| * operation on the files in each folder of the tree. |
| * |
| * @param starteamFolder the StarTeam folder from which files to be |
| * checked out |
| * @param targetFolder the local mapping of rootStarteamFolder |
| * @exception BuildException if any error occurs |
| */ |
| protected void visit(Folder starteamFolder, java.io.File targetFolder) |
| throws BuildException { |
| try { |
| |
| |
| if (null != getRootLocalFolder()) { |
| starteamFolder.setAlternatePathFragment( |
| targetFolder.getAbsolutePath()); |
| } |
| |
| if (!targetFolder.exists()) { |
| if (!this.isUsingRevisionLabel()) { |
| if (this.createDirs) { |
| if (targetFolder.mkdirs()) { |
| log("Creating folder: " + targetFolder); |
| } else { |
| throw new BuildException( |
| "Failed to create local folder " + targetFolder); |
| } |
| } |
| } |
| } |
| |
| |
| Folder[] foldersList = starteamFolder.getSubFolders(); |
| Item[] filesList = starteamFolder.getItems(getTypeNames().FILE); |
| |
| if (this.isUsingRevisionLabel()) { |
| |
| // prune away any files not belonging to the revision label |
| // this is one ugly API from Starteam SDK |
| |
| Hashtable labelItems = new Hashtable(filesList.length); |
| int s = filesList.length; |
| int[] ids = new int[s]; |
| for (int i = 0; i < s; i++) { |
| ids[i] = filesList[i].getItemID(); |
| labelItems.put(new Integer(ids[i]), new Integer(i)); |
| } |
| int[] foundIds = getLabelInUse().getLabeledItemIDs(ids); |
| s = foundIds.length; |
| Item[] labeledFiles = new Item[s]; |
| for (int i = 0; i < s; i++) { |
| Integer id = new Integer(foundIds[i]); |
| labeledFiles[i] = |
| filesList[((Integer) labelItems.get(id)).intValue()]; |
| } |
| filesList = labeledFiles; |
| } |
| |
| |
| // note, it's important to scan the items BEFORE we make the |
| // Unmatched file map because that creates a bunch of NEW |
| // folders and files (unattached to repository) and we |
| // don't want to include those in our traversal. |
| |
| UnmatchedFileMap ufm = |
| new CheckoutMap(). |
| init(targetFolder.getAbsoluteFile(), starteamFolder); |
| |
| |
| |
| for (int i = 0; i < foldersList.length; i++) { |
| Folder stFolder = foldersList[i]; |
| |
| java.io.File subfolder = |
| new java.io.File(targetFolder, stFolder.getName()); |
| |
| ufm.removeControlledItem(subfolder); |
| |
| if (isRecursive()) { |
| visit(stFolder, subfolder); |
| } |
| } |
| |
| for (int i = 0; i < filesList.length; i++) { |
| com.starbase.starteam.File stFile = |
| (com.starbase.starteam.File) filesList[i]; |
| processFile(stFile, targetFolder); |
| |
| ufm.removeControlledItem( |
| new java.io.File(targetFolder, stFile.getName())); |
| } |
| if (this.deleteUncontrolled) { |
| ufm.processUncontrolledItems(); |
| } |
| } catch (IOException e) { |
| throw new BuildException(e); |
| } |
| } |
| |
| |
| /** |
| * provides a string showing from and to full paths for logging |
| * |
| * @param remotefile the Star Team file being processed. |
| * |
| * @return a string showing from and to full paths |
| */ |
| private String describeCheckout(com.starbase.starteam.File remotefile, |
| java.io.File localFile) { |
| StringBuffer sb = new StringBuffer(); |
| sb.append(getFullRepositoryPath(remotefile)) |
| .append(" --> "); |
| if (null == localFile) { |
| sb.append(remotefile.getFullName()); |
| } else { |
| sb.append(localFile); |
| } |
| return sb.toString(); |
| } |
| private String describeCheckout(com.starbase.starteam.File remotefile) { |
| return describeCheckout(remotefile, null); |
| } |
| /** |
| * Processes (checks out) <code>stFiles</code>files from StarTeam folder. |
| * |
| * @param eachFile repository file to process |
| * @param targetFolder a java.io.File (Folder) to work |
| * @throws IOException when StarTeam API fails to work with files |
| */ |
| private void processFile(com.starbase.starteam.File eachFile, |
| File targetFolder) |
| throws IOException { |
| String filename = eachFile.getName(); |
| |
| java.io.File localFile = new java.io.File(targetFolder, filename); |
| |
| // If the file doesn't pass the include/exclude tests, skip it. |
| if (!shouldProcess(filename)) { |
| log("Excluding " + getFullRepositoryPath(eachFile), |
| Project.MSG_INFO); |
| return; |
| } |
| |
| if (this.isUsingRevisionLabel()) { |
| if (!targetFolder.exists()) { |
| if (targetFolder.mkdirs()) { |
| log("Creating folder: " + targetFolder); |
| } else { |
| throw new BuildException( |
| "Failed to create local folder " + targetFolder); |
| } |
| } |
| boolean success = eachFile.checkoutByLabelID( |
| localFile, |
| getIDofLabelInUse(), |
| this.lockStatus, |
| !this.useRepositoryTimeStamp, |
| true, |
| false); |
| if (success) { |
| log("Checked out " + describeCheckout(eachFile, localFile)); |
| } |
| } else { |
| boolean checkout = true; |
| |
| // Just a note: StarTeam has a status for NEW which implies |
| // that there is an item on your local machine that is not |
| // in the repository. These are the items that show up as |
| // NOT IN VIEW in the Starteam GUI. |
| // One would think that we would want to perhaps checkin the |
| // NEW items (not in all cases! - Steve Cohen 15 Dec 2001) |
| // Unfortunately, the sdk doesn't really work, and we can't |
| // actually see anything with a status of NEW. That is why |
| // we can just check out everything here without worrying |
| // about losing anything. |
| |
| int fileStatus = (eachFile.getStatus()); |
| |
| // We try to update the status once to give StarTeam |
| // another chance. |
| |
| if (fileStatus == Status.MERGE |
| || fileStatus == Status.UNKNOWN) { |
| eachFile.updateStatus(true, true); |
| fileStatus = (eachFile.getStatus()); |
| } |
| |
| log(eachFile.toString() + " has status of " |
| + Status.name(fileStatus), Project.MSG_DEBUG); |
| |
| |
| switch (fileStatus) { |
| case Status.OUTOFDATE: |
| case Status.MISSING: |
| log("Checking out: " + describeCheckout(eachFile)); |
| break; |
| default: |
| if (isForced() && fileStatus != Status.CURRENT) { |
| log("Forced checkout of " |
| + describeCheckout(eachFile) |
| + " over status " + Status.name(fileStatus)); |
| } else { |
| log("Skipping: " + getFullRepositoryPath(eachFile) |
| + " - status: " + Status.name(fileStatus)); |
| checkout = false; |
| } |
| } |
| |
| if (checkout) { |
| if (!targetFolder.exists()) { |
| if (targetFolder.mkdirs()) { |
| log("Creating folder: " + targetFolder); |
| } else { |
| throw new BuildException( |
| "Failed to create local folder " + targetFolder); |
| } |
| } |
| eachFile.checkout(this.lockStatus, |
| !this.useRepositoryTimeStamp, this.convertEOL, false); |
| } |
| } |
| } |
| /** |
| * handles the deletion of uncontrolled items |
| */ |
| private class CheckoutMap extends UnmatchedFileMap { |
| protected boolean isActive() { |
| return StarTeamCheckout.this.deleteUncontrolled; |
| } |
| |
| /** |
| * override of the base class init. It can be much simpler, since |
| * the action to be taken is simply to delete the local files. No |
| * further interaction with the repository is necessary. |
| * |
| * @param localFolder |
| * the local folder from which the mappings will be made. |
| * @param remoteFolder |
| * not used in this implementation |
| */ |
| UnmatchedFileMap init(java.io.File localFolder, Folder remoteFolder) { |
| if (!localFolder.exists()) { |
| return this; |
| } |
| |
| String[] localFiles = localFolder.list(); |
| // PR 31965 says that it can return null |
| if (localFiles == null) { |
| return this; |
| } |
| for (int i = 0; i < localFiles.length; i++) { |
| java.io.File localFile = |
| new java.io.File(localFolder, localFiles[i]).getAbsoluteFile(); |
| |
| log("adding " + localFile + " to UnmatchedFileMap", |
| Project.MSG_DEBUG); |
| |
| if (localFile.isDirectory()) { |
| this.put(localFile, ""); |
| } else { |
| this.put(localFile, ""); |
| } |
| } |
| return this; |
| } |
| |
| |
| |
| /** |
| * deletes uncontrolled items from the local tree. It is assumed |
| * that this method will not be called until all the items in the |
| * corresponding folder have been processed, and that the internal map |
| * will contain only uncontrolled items. |
| */ |
| void processUncontrolledItems() throws BuildException { |
| if (this.isActive()) { |
| Enumeration e = this.keys(); |
| while (e.hasMoreElements()) { |
| java.io.File local = (java.io.File) e.nextElement(); |
| delete(local); |
| } |
| } |
| } |
| |
| /** |
| * deletes all files and if the file is a folder recursively deletes |
| * everything in it. |
| * |
| * @param local The local file or folder to be deleted. |
| */ |
| void delete(java.io.File local) { |
| // once we find a folder that isn't in the repository, |
| // anything below it can be deleted. |
| if (local.isDirectory() && isRecursive()) { |
| String[] contents = local.list(); |
| for (int i = 0; i < contents.length; i++) { |
| java.io.File file = new java.io.File(local, contents[i]); |
| delete(file); |
| } |
| } |
| local.delete(); |
| log("Deleted uncontrolled item " + local.getAbsolutePath()); |
| } |
| } |
| |
| |
| } |