001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.tools.ant.taskdefs.optional.starteam;
019:
020: import com.starbase.starteam.Folder;
021: import com.starbase.starteam.Item;
022: import com.starbase.starteam.Status;
023: import com.starbase.starteam.View;
024: import com.starbase.starteam.ViewConfiguration;
025: import java.io.IOException;
026: import java.io.File;
027: import java.util.Enumeration;
028: import java.util.Hashtable;
029: import org.apache.tools.ant.BuildException;
030: import org.apache.tools.ant.Project;
031:
032: /**
033: * Checks out files from a StarTeam project.
034: * It also creates all working directories on the
035: * local directory if appropriate. Ant Usage:
036: * <pre>
037: * <taskdef name="starteamcheckout"
038: * classname="org.apache.tools.ant.taskdefs.StarTeamCheckout"/>
039: * <starteamcheckout username="BuildMaster" password="ant" starteamFolder="Source"
040: * starteamurl="servername:portnum/project/view"
041: * createworkingdirectories="true"/>
042: * </pre>
043: *
044: * @version 1.1
045: * @see <a href="http://www.borland.com/us/products/starteam/index.html"
046: * >borland StarTeam Web Site</a>
047: *
048: * @ant.task name="stcheckout" category="scm"
049: */
050: public class StarTeamCheckout extends TreeBasedTask {
051:
052: /**
053: * holder for the createDirs attribute
054: */
055: private boolean createDirs = true;
056:
057: /**
058: * holder for the deleteUncontrolled attribute. If true,
059: * all local files not in StarTeam will be deleted.
060: */
061: private boolean deleteUncontrolled = true;
062:
063: /**
064: * holder for the deleteUncontrolled attribute. If true,
065: * (default) local non-binary files will be checked out using the local
066: * platform's EOL convention. If false, checkouts will preserve the
067: * server's EOL convention.
068: */
069: private boolean convertEOL = true;
070:
071: /**
072: * flag (defaults to true) to create all directories
073: * that are in the Starteam repository even if they are empty.
074: *
075: * @param value the value to set the attribute to.
076: */
077: public void setCreateWorkingDirs(boolean value) {
078: this .createDirs = value;
079: }
080:
081: /**
082: * Whether or not all local files <i>not<i> in StarTeam should be deleted.
083: * Optional, defaults to <code>true</code>.
084: * @param value the value to set the attribute to.
085: */
086: public void setDeleteUncontrolled(boolean value) {
087: this .deleteUncontrolled = value;
088: }
089:
090: /**
091: * Set whether or not files should be checked out using the
092: * local machine's EOL convention.
093: * Optional, defaults to <code>true</code>.
094: * @param value the value to set the attribute to.
095: */
096: public void setConvertEOL(boolean value) {
097: this .convertEOL = value;
098: }
099:
100: /**
101: * Sets the label StarTeam is to use for checkout; defaults to the most recent file.
102: * The label must exist in starteam or an exception will be thrown.
103: * @param label the label to be used
104: */
105: public void setLabel(String label) {
106: _setLabel(label);
107: }
108:
109: /**
110: * This attribute tells whether to do a locked checkout, an unlocked
111: * checkout or to leave the checkout status alone (default). A locked
112: * checkout locks all other users out from making changes. An unlocked
113: * checkout reverts all local files to their previous repository status
114: * and removes the lock.
115: * @see #setLocked(boolean)
116: * @see #setUnlocked(boolean)
117: */
118: private int lockStatus = Item.LockType.UNCHANGED;
119:
120: /**
121: * Set to do a locked checkout; optional default is false.
122: * @param v True to do a locked checkout, false to checkout without
123: * changing status/.
124: * @exception BuildException if both locked and unlocked are set true
125: */
126: public void setLocked(boolean v) throws BuildException {
127: setLockStatus(v, Item.LockType.EXCLUSIVE);
128: }
129:
130: /**
131: * Set to do an unlocked checkout. Default is false;
132: * @param v True to do an unlocked checkout, false to checkout without
133: * changing status.
134: * @exception BuildException if both locked and unlocked are set true
135: */
136: public void setUnlocked(boolean v) throws BuildException {
137: setLockStatus(v, Item.LockType.UNLOCKED);
138: }
139:
140: private void setLockStatus(boolean v, int newStatus)
141: throws BuildException {
142: if (v) {
143: if (this .lockStatus == Item.LockType.UNCHANGED) {
144: this .lockStatus = newStatus;
145: } else if (this .lockStatus != newStatus) {
146: throw new BuildException(
147: "Error: cannot set locked and unlocked both true.");
148: }
149: }
150: }
151:
152: /**
153: * should checked out files get the timestamp from the repository
154: * or the time they are checked out. True means use the repository
155: * timestamp.
156: */
157: private boolean useRepositoryTimeStamp = false;
158:
159: /**
160: * sets the useRepositoryTimestmp member.
161: *
162: * @param useRepositoryTimeStamp
163: * true means checked out files will get the repository timestamp.
164: * false means the checked out files will be timestamped at the time
165: * of checkout.
166: */
167: public void setUseRepositoryTimeStamp(boolean useRepositoryTimeStamp) {
168: this .useRepositoryTimeStamp = useRepositoryTimeStamp;
169: }
170:
171: /**
172: * returns the value of the useRepositoryTimestamp member
173: *
174: * @return the value of the useRepositoryTimestamp member
175: */
176: public boolean getUseRepositoryTimeStamp() {
177: return this .useRepositoryTimeStamp;
178: }
179:
180: /**
181: * List files, dates, and statuses as of this date; optional.
182: * If not specified, the most recent version of each file will be listed.
183: *
184: * @param asOfDateParam the date as of which the listing to be made
185: * @since Ant 1.6
186: */
187: public void setAsOfDate(String asOfDateParam) {
188: _setAsOfDate(asOfDateParam);
189: }
190:
191: /**
192: * Date Format with which asOfDate parameter to be parsed; optional.
193: * Must be a SimpleDateFormat compatible string.
194: * If not specified, and asOfDateParam is specified, parse will use ISO8601
195: * datetime and date formats.
196: *
197: * @param asOfDateFormat the SimpleDateFormat-compatible format string
198: * @since Ant 1.6
199: */
200: public void setAsOfDateFormat(String asOfDateFormat) {
201: _setAsOfDateFormat(asOfDateFormat);
202: }
203:
204: /**
205: * Override of base-class abstract function creates an
206: * appropriately configured view for checkouts - either
207: * the current view or a view from this.label or the raw
208: * view itself in the case of a revision label.
209: *
210: * @param raw the unconfigured <code>View</code>
211: *
212: * @return the snapshot <code>View</code> appropriately configured.
213: * @exception BuildException on error
214: */
215: protected View createSnapshotView(View raw) throws BuildException {
216:
217: int labelID = getLabelID(raw);
218:
219: // if a label has been supplied and it is a view label, use it
220: // to configure the view
221: if (this .isUsingViewLabel()) {
222: return new View(raw, ViewConfiguration
223: .createFromLabel(labelID));
224: // if a label has been supplied and it is a revision label, use the raw
225: // the view as the snapshot
226: } else if (this .isUsingRevisionLabel()) {
227: return raw;
228: }
229: // if a date has been supplied use a view configured to the date.
230: View view = getViewConfiguredByDate(raw);
231: if (view != null) {
232: return view;
233: // otherwise, use this view configured as the tip.
234: } else {
235: return new View(raw, ViewConfiguration.createTip());
236: }
237: }
238:
239: /**
240: * Implements base-class abstract function to define tests for
241: * any preconditons required by the task.
242: *
243: * @exception BuildException thrown if both rootLocalFolder
244: * and viewRootLocalFolder are defined
245: */
246: protected void testPreconditions() throws BuildException {
247: if (this .isUsingRevisionLabel() && this .createDirs) {
248: log(
249: "Ignoring createworkingdirs while using a revision label."
250: + " Folders will be created only as needed.",
251: Project.MSG_WARN);
252: this .createDirs = false;
253: }
254: if (lockStatus != Item.LockType.UNCHANGED) {
255: boolean lockStatusBad = false;
256: if (null != getLabel()) {
257: log("Neither locked nor unlocked may be true"
258: + " when checking out a labeled version.",
259: Project.MSG_ERR);
260: lockStatusBad = true;
261: } else if (null != getAsOfDate()) {
262: log("Neither locked nor unlocked may be true"
263: + " when checking out by date.",
264: Project.MSG_ERR);
265: lockStatusBad = true;
266: }
267: if (lockStatusBad) {
268: throw new BuildException(
269: "Lock status may not be changed"
270: + " when checking out a non-current version.");
271: }
272: }
273: if (null != getLabel() && null != getAsOfDate()) {
274: throw new BuildException(
275: "Both label and asOfDate specified. "
276: + "Unable to process request.");
277: }
278:
279: }
280:
281: /**
282: * extenders should emit to the log an entry describing the parameters
283: * that will be used by this operation.
284: *
285: * @param starteamrootFolder
286: * root folder in StarTeam for the operation
287: * @param targetrootFolder
288: * root local folder for the operation (whether specified
289: * by the user or not.
290: */
291:
292: protected void logOperationDescription(Folder starteamrootFolder,
293: java.io.File targetrootFolder) {
294: log((this .isRecursive() ? "Recursive" : "Non-recursive")
295: + " Checkout from: "
296: + starteamrootFolder.getFolderHierarchy());
297:
298: log(" Checking out to"
299: + (null == getRootLocalFolder() ? "(default): " : ": ")
300: + targetrootFolder.getAbsolutePath());
301:
302: logLabel();
303: logAsOfDate();
304: logIncludes();
305: logExcludes();
306:
307: if (this .lockStatus == Item.LockType.EXCLUSIVE) {
308: log(" Items will be checked out with Exclusive locks.");
309: } else if (this .lockStatus == Item.LockType.UNLOCKED) {
310: log(" Items will be checked out unlocked "
311: + "(even if presently locked).");
312: } else {
313: log(" Items will be checked out with no change in lock status.");
314: }
315: log(" Items will be checked out with "
316: + (this .useRepositoryTimeStamp ? "repository timestamps."
317: : "the current timestamp."));
318: log(" Items will be checked out "
319: + (this .isForced() ? "regardless of"
320: : "in accordance with") + " repository status.");
321: if (this .deleteUncontrolled) {
322: log(" Local items not found in the repository will be deleted.");
323: }
324: log(" Items will be checked out "
325: + (this .convertEOL ? "using the local machine's EOL convention"
326: : "without changing the EOL convention used on the server"));
327: log(" Directories will be created"
328: + (this .createDirs ? " wherever they exist in the repository, even if empty."
329: : " only where needed to check out files."));
330:
331: }
332:
333: /**
334: * Implements base-class abstract function to perform the checkout
335: * operation on the files in each folder of the tree.
336: *
337: * @param starteamFolder the StarTeam folder from which files to be
338: * checked out
339: * @param targetFolder the local mapping of rootStarteamFolder
340: * @exception BuildException if any error occurs
341: */
342: protected void visit(Folder starteamFolder,
343: java.io.File targetFolder) throws BuildException {
344: try {
345:
346: if (null != getRootLocalFolder()) {
347: starteamFolder.setAlternatePathFragment(targetFolder
348: .getAbsolutePath());
349: }
350:
351: if (!targetFolder.exists()) {
352: if (!this .isUsingRevisionLabel()) {
353: if (this .createDirs) {
354: if (targetFolder.mkdirs()) {
355: log("Creating folder: " + targetFolder);
356: } else {
357: throw new BuildException(
358: "Failed to create local folder "
359: + targetFolder);
360: }
361: }
362: }
363: }
364:
365: Folder[] foldersList = starteamFolder.getSubFolders();
366: Item[] filesList = starteamFolder
367: .getItems(getTypeNames().FILE);
368:
369: if (this .isUsingRevisionLabel()) {
370:
371: // prune away any files not belonging to the revision label
372: // this is one ugly API from Starteam SDK
373:
374: Hashtable labelItems = new Hashtable(filesList.length);
375: int s = filesList.length;
376: int[] ids = new int[s];
377: for (int i = 0; i < s; i++) {
378: ids[i] = filesList[i].getItemID();
379: labelItems.put(new Integer(ids[i]), new Integer(i));
380: }
381: int[] foundIds = getLabelInUse().getLabeledItemIDs(ids);
382: s = foundIds.length;
383: Item[] labeledFiles = new Item[s];
384: for (int i = 0; i < s; i++) {
385: Integer id = new Integer(foundIds[i]);
386: labeledFiles[i] = filesList[((Integer) labelItems
387: .get(id)).intValue()];
388: }
389: filesList = labeledFiles;
390: }
391:
392: // note, it's important to scan the items BEFORE we make the
393: // Unmatched file map because that creates a bunch of NEW
394: // folders and files (unattached to repository) and we
395: // don't want to include those in our traversal.
396:
397: UnmatchedFileMap ufm = new CheckoutMap().init(targetFolder
398: .getAbsoluteFile(), starteamFolder);
399:
400: for (int i = 0; i < foldersList.length; i++) {
401: Folder stFolder = foldersList[i];
402:
403: java.io.File subfolder = new java.io.File(targetFolder,
404: stFolder.getName());
405:
406: ufm.removeControlledItem(subfolder);
407:
408: if (isRecursive()) {
409: visit(stFolder, subfolder);
410: }
411: }
412:
413: for (int i = 0; i < filesList.length; i++) {
414: com.starbase.starteam.File stFile = (com.starbase.starteam.File) filesList[i];
415: processFile(stFile, targetFolder);
416:
417: ufm.removeControlledItem(new java.io.File(targetFolder,
418: stFile.getName()));
419: }
420: if (this .deleteUncontrolled) {
421: ufm.processUncontrolledItems();
422: }
423: } catch (IOException e) {
424: throw new BuildException(e);
425: }
426: }
427:
428: /**
429: * provides a string showing from and to full paths for logging
430: *
431: * @param remotefile the Star Team file being processed.
432: *
433: * @return a string showing from and to full paths
434: */
435: private String describeCheckout(
436: com.starbase.starteam.File remotefile,
437: java.io.File localFile) {
438: StringBuffer sb = new StringBuffer();
439: sb.append(getFullRepositoryPath(remotefile)).append(" --> ");
440: if (null == localFile) {
441: sb.append(remotefile.getFullName());
442: } else {
443: sb.append(localFile);
444: }
445: return sb.toString();
446: }
447:
448: private String describeCheckout(
449: com.starbase.starteam.File remotefile) {
450: return describeCheckout(remotefile, null);
451: }
452:
453: /**
454: * Processes (checks out) <code>stFiles</code>files from StarTeam folder.
455: *
456: * @param eachFile repository file to process
457: * @param targetFolder a java.io.File (Folder) to work
458: * @throws IOException when StarTeam API fails to work with files
459: */
460: private void processFile(com.starbase.starteam.File eachFile,
461: File targetFolder) throws IOException {
462: String filename = eachFile.getName();
463:
464: java.io.File localFile = new java.io.File(targetFolder,
465: filename);
466:
467: // If the file doesn't pass the include/exclude tests, skip it.
468: if (!shouldProcess(filename)) {
469: log("Excluding " + getFullRepositoryPath(eachFile),
470: Project.MSG_INFO);
471: return;
472: }
473:
474: if (this .isUsingRevisionLabel()) {
475: if (!targetFolder.exists()) {
476: if (targetFolder.mkdirs()) {
477: log("Creating folder: " + targetFolder);
478: } else {
479: throw new BuildException(
480: "Failed to create local folder "
481: + targetFolder);
482: }
483: }
484: boolean success = eachFile.checkoutByLabelID(localFile,
485: getIDofLabelInUse(), this .lockStatus,
486: !this .useRepositoryTimeStamp, true, false);
487: if (success) {
488: log("Checked out "
489: + describeCheckout(eachFile, localFile));
490: }
491: } else {
492: boolean checkout = true;
493:
494: // Just a note: StarTeam has a status for NEW which implies
495: // that there is an item on your local machine that is not
496: // in the repository. These are the items that show up as
497: // NOT IN VIEW in the Starteam GUI.
498: // One would think that we would want to perhaps checkin the
499: // NEW items (not in all cases! - Steve Cohen 15 Dec 2001)
500: // Unfortunately, the sdk doesn't really work, and we can't
501: // actually see anything with a status of NEW. That is why
502: // we can just check out everything here without worrying
503: // about losing anything.
504:
505: int fileStatus = (eachFile.getStatus());
506:
507: // We try to update the status once to give StarTeam
508: // another chance.
509:
510: if (fileStatus == Status.MERGE
511: || fileStatus == Status.UNKNOWN) {
512: eachFile.updateStatus(true, true);
513: fileStatus = (eachFile.getStatus());
514: }
515:
516: log(eachFile.toString() + " has status of "
517: + Status.name(fileStatus), Project.MSG_DEBUG);
518:
519: switch (fileStatus) {
520: case Status.OUTOFDATE:
521: case Status.MISSING:
522: log("Checking out: " + describeCheckout(eachFile));
523: break;
524: default:
525: if (isForced() && fileStatus != Status.CURRENT) {
526: log("Forced checkout of "
527: + describeCheckout(eachFile)
528: + " over status " + Status.name(fileStatus));
529: } else {
530: log("Skipping: " + getFullRepositoryPath(eachFile)
531: + " - status: " + Status.name(fileStatus));
532: checkout = false;
533: }
534: }
535:
536: if (checkout) {
537: if (!targetFolder.exists()) {
538: if (targetFolder.mkdirs()) {
539: log("Creating folder: " + targetFolder);
540: } else {
541: throw new BuildException(
542: "Failed to create local folder "
543: + targetFolder);
544: }
545: }
546: eachFile.checkout(this .lockStatus,
547: !this .useRepositoryTimeStamp, this .convertEOL,
548: false);
549: }
550: }
551: }
552:
553: /**
554: * handles the deletion of uncontrolled items
555: */
556: private class CheckoutMap extends UnmatchedFileMap {
557: protected boolean isActive() {
558: return StarTeamCheckout.this .deleteUncontrolled;
559: }
560:
561: /**
562: * override of the base class init. It can be much simpler, since
563: * the action to be taken is simply to delete the local files. No
564: * further interaction with the repository is necessary.
565: *
566: * @param localFolder
567: * the local folder from which the mappings will be made.
568: * @param remoteFolder
569: * not used in this implementation
570: */
571: UnmatchedFileMap init(java.io.File localFolder,
572: Folder remoteFolder) {
573: if (!localFolder.exists()) {
574: return this ;
575: }
576:
577: String[] localFiles = localFolder.list();
578: // PR 31965 says that it can return null
579: if (localFiles == null) {
580: return this ;
581: }
582: for (int i = 0; i < localFiles.length; i++) {
583: java.io.File localFile = new java.io.File(localFolder,
584: localFiles[i]).getAbsoluteFile();
585:
586: log("adding " + localFile + " to UnmatchedFileMap",
587: Project.MSG_DEBUG);
588:
589: if (localFile.isDirectory()) {
590: this .put(localFile, "");
591: } else {
592: this .put(localFile, "");
593: }
594: }
595: return this ;
596: }
597:
598: /**
599: * deletes uncontrolled items from the local tree. It is assumed
600: * that this method will not be called until all the items in the
601: * corresponding folder have been processed, and that the internal map
602: * will contain only uncontrolled items.
603: */
604: void processUncontrolledItems() throws BuildException {
605: if (this .isActive()) {
606: Enumeration e = this .keys();
607: while (e.hasMoreElements()) {
608: java.io.File local = (java.io.File) e.nextElement();
609: delete(local);
610: }
611: }
612: }
613:
614: /**
615: * deletes all files and if the file is a folder recursively deletes
616: * everything in it.
617: *
618: * @param local The local file or folder to be deleted.
619: */
620: void delete(java.io.File local) {
621: // once we find a folder that isn't in the repository,
622: // anything below it can be deleted.
623: if (local.isDirectory() && isRecursive()) {
624: String[] contents = local.list();
625: for (int i = 0; i < contents.length; i++) {
626: java.io.File file = new java.io.File(local,
627: contents[i]);
628: delete(file);
629: }
630: }
631: local.delete();
632: log("Deleted uncontrolled item " + local.getAbsolutePath());
633: }
634: }
635:
636: }
|