summaryrefslogtreecommitdiff
path: root/src/main/org/apache/tools/ant/taskdefs/optional/starteam/StarTeamCheckout.java
blob: 4005b0819fac7de93c64477ef1dd3061c38637a1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
/*
 * Copyright  2001-2004 Apache Software Foundation
 *
 *  Licensed 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>
 * &lt;taskdef name="starteamcheckout"
 * classname="org.apache.tools.ant.taskdefs.StarTeamCheckout"/&gt;
 * &lt;starteamcheckout username="BuildMaster" password="ant" starteamFolder="Source"
 * starteamurl="servername:portnum/project/view"
 * createworkingdirectories="true"/&gt;
 * </pre>
 *
 * @author Christopher Charlier, ThoughtWorks, Inc. 2001
 * @author <a href="mailto:jcyip@thoughtworks.com">Jason Yip</a>
 * @author Jason Pettiss
 * @author <a href="mailto:stevec@ignitesports.com">Steve Cohen</a>
 * @version 1.1
 * @see <A HREF="http://www.starbase.com/">StarBase 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
     */
    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()) {
                    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, true);
            }
        }
    }
    /**
     * 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();

            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());
        }
    }


}