1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs.optional.starteam;
  18. import com.starbase.starteam.Folder;
  19. import com.starbase.starteam.Item;
  20. import com.starbase.starteam.Status;
  21. import com.starbase.starteam.View;
  22. import com.starbase.starteam.ViewConfiguration;
  23. import java.io.IOException;
  24. import java.io.File;
  25. import java.util.Enumeration;
  26. import java.util.Hashtable;
  27. import org.apache.tools.ant.BuildException;
  28. import org.apache.tools.ant.Project;
  29. /**
  30. * Checks out files from a StarTeam project.
  31. * It also creates all working directories on the
  32. * local directory if appropriate. Ant Usage:
  33. * <pre>
  34. * <taskdef name="starteamcheckout"
  35. * classname="org.apache.tools.ant.taskdefs.StarTeamCheckout"/>
  36. * <starteamcheckout username="BuildMaster" password="ant" starteamFolder="Source"
  37. * starteamurl="servername:portnum/project/view"
  38. * createworkingdirectories="true"/>
  39. * </pre>
  40. *
  41. * @version 1.1
  42. * @see <A HREF="http://www.starbase.com/">StarBase Web Site</A>
  43. *
  44. * @ant.task name="stcheckout" category="scm"
  45. */
  46. public class StarTeamCheckout extends TreeBasedTask {
  47. /**
  48. * holder for the createDirs attribute
  49. */
  50. private boolean createDirs = true;
  51. /**
  52. * holder for the deleteUncontrolled attribute. If true,
  53. * all local files not in StarTeam will be deleted.
  54. */
  55. private boolean deleteUncontrolled = true;
  56. /**
  57. * holder for the deleteUncontrolled attribute. If true,
  58. * (default) local non-binary files will be checked out using the local
  59. * platform's EOL convention. If false, checkouts will preserve the
  60. * server's EOL convention.
  61. */
  62. private boolean convertEOL = true;
  63. /**
  64. * flag (defaults to true) to create all directories
  65. * that are in the Starteam repository even if they are empty.
  66. *
  67. * @param value the value to set the attribute to.
  68. */
  69. public void setCreateWorkingDirs(boolean value) {
  70. this.createDirs = value;
  71. }
  72. /**
  73. * Whether or not all local files <i>not<i> in StarTeam should be deleted.
  74. * Optional, defaults to <code>true</code>.
  75. * @param value the value to set the attribute to.
  76. */
  77. public void setDeleteUncontrolled(boolean value) {
  78. this.deleteUncontrolled = value;
  79. }
  80. /**
  81. * Set whether or not files should be checked out using the
  82. * local machine's EOL convention.
  83. * Optional, defaults to <code>true</code>.
  84. * @param value the value to set the attribute to.
  85. */
  86. public void setConvertEOL(boolean value) {
  87. this.convertEOL = value;
  88. }
  89. /**
  90. * Sets the label StarTeam is to use for checkout; defaults to the most recent file.
  91. * The label must exist in starteam or an exception will be thrown.
  92. * @param label the label to be used
  93. */
  94. public void setLabel(String label) {
  95. _setLabel(label);
  96. }
  97. /**
  98. * This attribute tells whether to do a locked checkout, an unlocked
  99. * checkout or to leave the checkout status alone (default). A locked
  100. * checkout locks all other users out from making changes. An unlocked
  101. * checkout reverts all local files to their previous repository status
  102. * and removes the lock.
  103. * @see #setLocked(boolean)
  104. * @see #setUnlocked(boolean)
  105. */
  106. private int lockStatus = Item.LockType.UNCHANGED;
  107. /**
  108. * Set to do a locked checkout; optional default is false.
  109. * @param v True to do a locked checkout, false to checkout without
  110. * changing status/.
  111. * @exception BuildException if both locked and unlocked are set true
  112. */
  113. public void setLocked(boolean v) throws BuildException {
  114. setLockStatus(v, Item.LockType.EXCLUSIVE);
  115. }
  116. /**
  117. * Set to do an unlocked checkout. Default is false;
  118. * @param v True to do an unlocked checkout, false to checkout without
  119. * changing status.
  120. * @exception BuildException if both locked and unlocked are set true
  121. */
  122. public void setUnlocked(boolean v) throws BuildException {
  123. setLockStatus(v, Item.LockType.UNLOCKED);
  124. }
  125. private void setLockStatus(boolean v, int newStatus)
  126. throws BuildException {
  127. if (v) {
  128. if (this.lockStatus == Item.LockType.UNCHANGED) {
  129. this.lockStatus = newStatus;
  130. } else if (this.lockStatus != newStatus) {
  131. throw new BuildException(
  132. "Error: cannot set locked and unlocked both true.");
  133. }
  134. }
  135. }
  136. /**
  137. * should checked out files get the timestamp from the repository
  138. * or the time they are checked out. True means use the repository
  139. * timestamp.
  140. */
  141. private boolean useRepositoryTimeStamp = false;
  142. /**
  143. * sets the useRepositoryTimestmp member.
  144. *
  145. * @param useRepositoryTimeStamp
  146. * true means checked out files will get the repository timestamp.
  147. * false means the checked out files will be timestamped at the time
  148. * of checkout.
  149. */
  150. public void setUseRepositoryTimeStamp(boolean useRepositoryTimeStamp) {
  151. this.useRepositoryTimeStamp = useRepositoryTimeStamp;
  152. }
  153. /**
  154. * returns the value of the useRepositoryTimestamp member
  155. *
  156. * @return the value of the useRepositoryTimestamp member
  157. */
  158. public boolean getUseRepositoryTimeStamp() {
  159. return this.useRepositoryTimeStamp;
  160. }
  161. /**
  162. * List files, dates, and statuses as of this date; optional.
  163. * If not specified, the most recent version of each file will be listed.
  164. *
  165. * @param asOfDateParam the date as of which the listing to be made
  166. * @since Ant 1.6
  167. */
  168. public void setAsOfDate(String asOfDateParam) {
  169. _setAsOfDate(asOfDateParam);
  170. }
  171. /**
  172. * Date Format with which asOfDate parameter to be parsed; optional.
  173. * Must be a SimpleDateFormat compatible string.
  174. * If not specified, and asOfDateParam is specified, parse will use ISO8601
  175. * datetime and date formats.
  176. *
  177. * @param asOfDateFormat the SimpleDateFormat-compatible format string
  178. * @since Ant 1.6
  179. */
  180. public void setAsOfDateFormat(String asOfDateFormat) {
  181. _setAsOfDateFormat(asOfDateFormat);
  182. }
  183. /**
  184. * Override of base-class abstract function creates an
  185. * appropriately configured view for checkouts - either
  186. * the current view or a view from this.label or the raw
  187. * view itself in the case of a revision label.
  188. *
  189. * @param raw the unconfigured <code>View</code>
  190. *
  191. * @return the snapshot <code>View</code> appropriately configured.
  192. * @exception BuildException
  193. */
  194. protected View createSnapshotView(View raw) throws BuildException {
  195. int labelID = getLabelID(raw);
  196. // if a label has been supplied and it is a view label, use it
  197. // to configure the view
  198. if (this.isUsingViewLabel()) {
  199. return new View(raw, ViewConfiguration.createFromLabel(labelID));
  200. }
  201. // if a label has been supplied and it is a revision label, use the raw
  202. // the view as the snapshot
  203. else if (this.isUsingRevisionLabel()) {
  204. return raw;
  205. }
  206. // if a date has been supplied use a view configured to the date.
  207. View view = getViewConfiguredByDate(raw);
  208. if (view != null) {
  209. return view;
  210. }
  211. // otherwise, use this view configured as the tip.
  212. else {
  213. return new View(raw, ViewConfiguration.createTip());
  214. }
  215. }
  216. /**
  217. * Implements base-class abstract function to define tests for
  218. * any preconditons required by the task.
  219. *
  220. * @exception BuildException thrown if both rootLocalFolder
  221. * and viewRootLocalFolder are defined
  222. */
  223. protected void testPreconditions() throws BuildException {
  224. if (this.isUsingRevisionLabel() && this.createDirs) {
  225. log("Ignoring createworkingdirs while using a revision label."
  226. + " Folders will be created only as needed.",
  227. Project.MSG_WARN);
  228. this.createDirs = false;
  229. }
  230. if (lockStatus != Item.LockType.UNCHANGED) {
  231. boolean lockStatusBad = false;
  232. if (null != getLabel()) {
  233. log("Neither locked nor unlocked may be true"
  234. + " when checking out a labeled version.",
  235. Project.MSG_ERR);
  236. lockStatusBad = true;
  237. } else if (null != getAsOfDate()) {
  238. log("Neither locked nor unlocked may be true"
  239. + " when checking out by date.",
  240. Project.MSG_ERR);
  241. lockStatusBad = true;
  242. }
  243. if (lockStatusBad) {
  244. throw new BuildException(
  245. "Lock status may not be changed"
  246. + " when checking out a non-current version.");
  247. }
  248. }
  249. if (null != getLabel() && null != getAsOfDate()) {
  250. throw new BuildException(
  251. "Both label and asOfDate specified. "
  252. + "Unable to process request.");
  253. }
  254. }
  255. /**
  256. * extenders should emit to the log an entry describing the parameters
  257. * that will be used by this operation.
  258. *
  259. * @param starteamrootFolder
  260. * root folder in StarTeam for the operation
  261. * @param targetrootFolder
  262. * root local folder for the operation (whether specified
  263. * by the user or not.
  264. */
  265. protected void logOperationDescription(
  266. Folder starteamrootFolder, java.io.File targetrootFolder) {
  267. log((this.isRecursive() ? "Recursive" : "Non-recursive")
  268. + " Checkout from: " + starteamrootFolder.getFolderHierarchy());
  269. log(" Checking out to"
  270. + (null == getRootLocalFolder() ? "(default): " : ": ")
  271. + targetrootFolder.getAbsolutePath());
  272. logLabel();
  273. logAsOfDate();
  274. logIncludes();
  275. logExcludes();
  276. if (this.lockStatus == Item.LockType.EXCLUSIVE) {
  277. log(" Items will be checked out with Exclusive locks.");
  278. } else if (this.lockStatus == Item.LockType.UNLOCKED) {
  279. log(" Items will be checked out unlocked "
  280. + "(even if presently locked).");
  281. } else {
  282. log(" Items will be checked out with no change in lock status.");
  283. }
  284. log(" Items will be checked out with "
  285. + (this.useRepositoryTimeStamp ? "repository timestamps."
  286. : "the current timestamp."));
  287. log(" Items will be checked out "
  288. + (this.isForced() ? "regardless of" : "in accordance with")
  289. + " repository status.");
  290. if (this.deleteUncontrolled) {
  291. log(" Local items not found in the repository will be deleted.");
  292. }
  293. log(" Items will be checked out "
  294. + (this.convertEOL ? "using the local machine's EOL convention"
  295. : "without changing the EOL convention used on the server"));
  296. log(" Directories will be created"
  297. + (this.createDirs ? " wherever they exist in the repository, even if empty."
  298. : " only where needed to check out files."));
  299. }
  300. /**
  301. * Implements base-class abstract function to perform the checkout
  302. * operation on the files in each folder of the tree.
  303. *
  304. * @param starteamFolder the StarTeam folder from which files to be
  305. * checked out
  306. * @param targetFolder the local mapping of rootStarteamFolder
  307. * @exception BuildException if any error occurs
  308. */
  309. protected void visit(Folder starteamFolder, java.io.File targetFolder)
  310. throws BuildException {
  311. try {
  312. if (null != getRootLocalFolder()) {
  313. starteamFolder.setAlternatePathFragment(
  314. targetFolder.getAbsolutePath());
  315. }
  316. if (!targetFolder.exists()) {
  317. if (!this.isUsingRevisionLabel()) {
  318. if (this.createDirs) {
  319. if (targetFolder.mkdirs()) {
  320. log("Creating folder: " + targetFolder);
  321. } else {
  322. throw new BuildException(
  323. "Failed to create local folder " + targetFolder);
  324. }
  325. }
  326. }
  327. }
  328. Folder[] foldersList = starteamFolder.getSubFolders();
  329. Item[] filesList = starteamFolder.getItems(getTypeNames().FILE);
  330. if (this.isUsingRevisionLabel()) {
  331. // prune away any files not belonging to the revision label
  332. // this is one ugly API from Starteam SDK
  333. Hashtable labelItems = new Hashtable(filesList.length);
  334. int s = filesList.length;
  335. int[] ids = new int[s];
  336. for (int i = 0; i < s; i++) {
  337. ids[i] = filesList[i].getItemID();
  338. labelItems.put(new Integer(ids[i]), new Integer(i));
  339. }
  340. int[] foundIds = getLabelInUse().getLabeledItemIDs(ids);
  341. s = foundIds.length;
  342. Item[] labeledFiles = new Item[s];
  343. for (int i = 0; i < s; i++) {
  344. Integer id = new Integer(foundIds[i]);
  345. labeledFiles[i] =
  346. filesList[((Integer) labelItems.get(id)).intValue()];
  347. }
  348. filesList = labeledFiles;
  349. }
  350. // note, it's important to scan the items BEFORE we make the
  351. // Unmatched file map because that creates a bunch of NEW
  352. // folders and files (unattached to repository) and we
  353. // don't want to include those in our traversal.
  354. UnmatchedFileMap ufm =
  355. new CheckoutMap().
  356. init(targetFolder.getAbsoluteFile(), starteamFolder);
  357. for (int i = 0; i < foldersList.length; i++) {
  358. Folder stFolder = foldersList[i];
  359. java.io.File subfolder =
  360. new java.io.File(targetFolder, stFolder.getName());
  361. ufm.removeControlledItem(subfolder);
  362. if (isRecursive()) {
  363. visit(stFolder, subfolder);
  364. }
  365. }
  366. for (int i = 0; i < filesList.length; i++) {
  367. com.starbase.starteam.File stFile =
  368. (com.starbase.starteam.File) filesList[i];
  369. processFile(stFile, targetFolder);
  370. ufm.removeControlledItem(
  371. new java.io.File(targetFolder, stFile.getName()));
  372. }
  373. if (this.deleteUncontrolled) {
  374. ufm.processUncontrolledItems();
  375. }
  376. } catch (IOException e) {
  377. throw new BuildException(e);
  378. }
  379. }
  380. /**
  381. * provides a string showing from and to full paths for logging
  382. *
  383. * @param remotefile the Star Team file being processed.
  384. *
  385. * @return a string showing from and to full paths
  386. */
  387. private String describeCheckout(com.starbase.starteam.File remotefile,
  388. java.io.File localFile) {
  389. StringBuffer sb = new StringBuffer();
  390. sb.append(getFullRepositoryPath(remotefile))
  391. .append(" --> ");
  392. if (null == localFile) {
  393. sb.append(remotefile.getFullName());
  394. } else {
  395. sb.append(localFile);
  396. }
  397. return sb.toString();
  398. }
  399. private String describeCheckout(com.starbase.starteam.File remotefile) {
  400. return describeCheckout(remotefile, null);
  401. }
  402. /**
  403. * Processes (checks out) <code>stFiles</code>files from StarTeam folder.
  404. *
  405. * @param eachFile repository file to process
  406. * @param targetFolder a java.io.File (Folder) to work
  407. * @throws IOException when StarTeam API fails to work with files
  408. */
  409. private void processFile(com.starbase.starteam.File eachFile,
  410. File targetFolder)
  411. throws IOException {
  412. String filename = eachFile.getName();
  413. java.io.File localFile = new java.io.File(targetFolder, filename);
  414. // If the file doesn't pass the include/exclude tests, skip it.
  415. if (!shouldProcess(filename)) {
  416. log("Excluding " + getFullRepositoryPath(eachFile),
  417. Project.MSG_INFO);
  418. return;
  419. }
  420. if (this.isUsingRevisionLabel()) {
  421. if (!targetFolder.exists()) {
  422. if (targetFolder.mkdirs()) {
  423. log("Creating folder: " + targetFolder);
  424. } else {
  425. throw new BuildException(
  426. "Failed to create local folder " + targetFolder);
  427. }
  428. }
  429. boolean success = eachFile.checkoutByLabelID(
  430. localFile,
  431. getIDofLabelInUse(),
  432. this.lockStatus,
  433. !this.useRepositoryTimeStamp,
  434. true,
  435. false);
  436. if (success) {
  437. log("Checked out " + describeCheckout(eachFile, localFile));
  438. }
  439. } else {
  440. boolean checkout = true;
  441. // Just a note: StarTeam has a status for NEW which implies
  442. // that there is an item on your local machine that is not
  443. // in the repository. These are the items that show up as
  444. // NOT IN VIEW in the Starteam GUI.
  445. // One would think that we would want to perhaps checkin the
  446. // NEW items (not in all cases! - Steve Cohen 15 Dec 2001)
  447. // Unfortunately, the sdk doesn't really work, and we can't
  448. // actually see anything with a status of NEW. That is why
  449. // we can just check out everything here without worrying
  450. // about losing anything.
  451. int fileStatus = (eachFile.getStatus());
  452. // We try to update the status once to give StarTeam
  453. // another chance.
  454. if (fileStatus == Status.MERGE
  455. || fileStatus == Status.UNKNOWN) {
  456. eachFile.updateStatus(true, true);
  457. fileStatus = (eachFile.getStatus());
  458. }
  459. log(eachFile.toString() + " has status of "
  460. + Status.name(fileStatus), Project.MSG_DEBUG);
  461. switch (fileStatus) {
  462. case Status.OUTOFDATE:
  463. case Status.MISSING:
  464. log("Checking out: " + describeCheckout(eachFile));
  465. break;
  466. default:
  467. if (isForced()) {
  468. log("Forced checkout of "
  469. + describeCheckout(eachFile)
  470. + " over status " + Status.name(fileStatus));
  471. } else {
  472. log("Skipping: " + getFullRepositoryPath(eachFile)
  473. + " - status: " + Status.name(fileStatus));
  474. checkout = false;
  475. }
  476. }
  477. if (checkout) {
  478. if (!targetFolder.exists()) {
  479. if (targetFolder.mkdirs()) {
  480. log("Creating folder: " + targetFolder);
  481. } else {
  482. throw new BuildException(
  483. "Failed to create local folder " + targetFolder);
  484. }
  485. }
  486. eachFile.checkout(this.lockStatus,
  487. !this.useRepositoryTimeStamp, this.convertEOL, true);
  488. }
  489. }
  490. }
  491. /**
  492. * handles the deletion of uncontrolled items
  493. */
  494. private class CheckoutMap extends UnmatchedFileMap {
  495. protected boolean isActive() {
  496. return StarTeamCheckout.this.deleteUncontrolled;
  497. }
  498. /**
  499. * override of the base class init. It can be much simpler, since
  500. * the action to be taken is simply to delete the local files. No
  501. * further interaction with the repository is necessary.
  502. *
  503. * @param localFolder
  504. * the local folder from which the mappings will be made.
  505. * @param remoteFolder
  506. * not used in this implementation
  507. */
  508. UnmatchedFileMap init(java.io.File localFolder, Folder remoteFolder) {
  509. if (!localFolder.exists()) {
  510. return this;
  511. }
  512. String[] localFiles = localFolder.list();
  513. for (int i = 0; i < localFiles.length; i++) {
  514. java.io.File localFile =
  515. new java.io.File(localFolder, localFiles[i]).getAbsoluteFile();
  516. log("adding " + localFile + " to UnmatchedFileMap",
  517. Project.MSG_DEBUG);
  518. if (localFile.isDirectory()) {
  519. this.put(localFile, "");
  520. } else {
  521. this.put(localFile, "");
  522. }
  523. }
  524. return this;
  525. }
  526. /**
  527. * deletes uncontrolled items from the local tree. It is assumed
  528. * that this method will not be called until all the items in the
  529. * corresponding folder have been processed, and that the internal map
  530. * will contain only uncontrolled items.
  531. */
  532. void processUncontrolledItems() throws BuildException {
  533. if (this.isActive()) {
  534. Enumeration e = this.keys();
  535. while (e.hasMoreElements()) {
  536. java.io.File local = (java.io.File) e.nextElement();
  537. delete(local);
  538. }
  539. }
  540. }
  541. /**
  542. * deletes all files and if the file is a folder recursively deletes
  543. * everything in it.
  544. *
  545. * @param local The local file or folder to be deleted.
  546. */
  547. void delete(java.io.File local) {
  548. // once we find a folder that isn't in the repository,
  549. // anything below it can be deleted.
  550. if (local.isDirectory() && isRecursive()) {
  551. String[] contents = local.list();
  552. for (int i = 0; i < contents.length; i++) {
  553. java.io.File file = new java.io.File(local, contents[i]);
  554. delete(file);
  555. }
  556. }
  557. local.delete();
  558. log("Deleted uncontrolled item " + local.getAbsolutePath());
  559. }
  560. }
  561. }