Coverage report

  %line %branch
nl.toolforge.karma.core.vc.cvsimpl.CVSRunner
86% 
92% 

 1  
 /*
 2  
 Karma core - Core of the Karma application
 3  
 Copyright (C) 2004  Toolforge <www.toolforge.nl>
 4  
 
 5  
 This library is free software; you can redistribute it and/or
 6  
 modify it under the terms of the GNU Lesser General Public
 7  
 License as published by the Free Software Foundation; either
 8  
 version 2.1 of the License, or (at your option) any later version.
 9  
 
 10  
 This library is distributed in the hope that it will be useful,
 11  
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 Lesser General Public License for more details.
 14  
 
 15  
 You should have received a copy of the GNU Lesser General Public
 16  
 License along with this library; if not, write to the Free Software
 17  
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 */
 19  
 package nl.toolforge.karma.core.vc.cvsimpl;
 20  
 
 21  
 import net.sf.sillyexceptions.OutOfTheBlueException;
 22  
 import nl.toolforge.core.util.file.MyFileUtils;
 23  
 import nl.toolforge.karma.core.ErrorCode;
 24  
 import nl.toolforge.karma.core.KarmaRuntimeException;
 25  
 import nl.toolforge.karma.core.Version;
 26  
 import nl.toolforge.karma.core.cmd.Command;
 27  
 import nl.toolforge.karma.core.cmd.CommandResponse;
 28  
 import nl.toolforge.karma.core.history.ModuleHistory;
 29  
 import nl.toolforge.karma.core.history.ModuleHistoryEvent;
 30  
 import nl.toolforge.karma.core.history.ModuleHistoryException;
 31  
 import nl.toolforge.karma.core.history.ModuleHistoryFactory;
 32  
 import nl.toolforge.karma.core.location.Location;
 33  
 import nl.toolforge.karma.core.module.Module;
 34  
 import nl.toolforge.karma.core.vc.Authenticator;
 35  
 import nl.toolforge.karma.core.vc.Authenticators;
 36  
 import nl.toolforge.karma.core.vc.DevelopmentLine;
 37  
 import nl.toolforge.karma.core.vc.PatchLine;
 38  
 import nl.toolforge.karma.core.vc.Runner;
 39  
 import nl.toolforge.karma.core.vc.SymbolicName;
 40  
 import nl.toolforge.karma.core.vc.VersionControlException;
 41  
 import nl.toolforge.karma.core.vc.VersionControlSystem;
 42  
 import org.apache.commons.logging.Log;
 43  
 import org.apache.commons.logging.LogFactory;
 44  
 import org.netbeans.lib.cvsclient.Client;
 45  
 import org.netbeans.lib.cvsclient.admin.StandardAdminHandler;
 46  
 import org.netbeans.lib.cvsclient.command.CommandException;
 47  
 import org.netbeans.lib.cvsclient.command.GlobalOptions;
 48  
 import org.netbeans.lib.cvsclient.command.add.AddCommand;
 49  
 import org.netbeans.lib.cvsclient.command.checkout.CheckoutCommand;
 50  
 import org.netbeans.lib.cvsclient.command.commit.CommitCommand;
 51  
 import org.netbeans.lib.cvsclient.command.class="keyword">importcmd.ImportCommand;
 52  
 import org.netbeans.lib.cvsclient.command.log.LogInformation;
 53  
 import org.netbeans.lib.cvsclient.command.log.RlogCommand;
 54  
 import org.netbeans.lib.cvsclient.command.tag.TagCommand;
 55  
 import org.netbeans.lib.cvsclient.command.update.UpdateCommand;
 56  
 import org.netbeans.lib.cvsclient.connection.AuthenticationException;
 57  
 import org.netbeans.lib.cvsclient.connection.Connection;
 58  
 import org.netbeans.lib.cvsclient.connection.ConnectionFactory;
 59  
 import org.netbeans.lib.cvsclient.connection.PServerConnection;
 60  
 import org.netbeans.lib.cvsclient.event.CVSListener;
 61  
 
 62  
 import java.io.File;
 63  
 import java.io.IOException;
 64  
 import java.util.ArrayList;
 65  
 import java.util.Collection;
 66  
 import java.util.Date;
 67  
 import java.util.Hashtable;
 68  
 import java.util.Iterator;
 69  
 import java.util.List;
 70  
 import java.util.Map;
 71  
 import java.util.StringTokenizer;
 72  
 
 73  
 /**
 74  
  * <p>Runner class for CVS. Executes stuff on a CVS repository.
 75  
  * <p/>
 76  
  * <p>TODO : the CVSRunner could be made multi-threaded, to use bandwidth to a remote repository much better ...
 77  
  *
 78  
  * @author D.A. Smedes
 79  
  * @version $Id: CVSRunner.java,v 1.33 2004/11/16 22:31:57 asmedes Exp $
 80  
  */
 81  
 public final class CVSRunner implements Runner {
 82  
 
 83  
   static {
 84  
 
 85  
     // As per the recommendation ...
 86  54
     //
 87  28
     System.setProperty("javacvs.multiple_commands_warning", "false");
 88  32
   }
 89  369
 
 90  203
   private CVSListener listener = null; // The listener that receives events from this runner.
 91  340
 
 92  56
   private static Log logger = LogFactory.getLog(CVSRunner.class);
 93  433
 
 94  572
   private GlobalOptions globalOptions = new GlobalOptions();
 95  435
   private Connection connection = null;
 96  261
 
 97  203
   private CVSRepository location = null;
 98  261
 
 99  203
   private boolean isExt = false;
 100  232
 
 101  
   /**
 102  
    * Constructs a runner to fire commands on a CVS repository. A typical client for a <code>CVSRunner</code> instance is
 103  
    * a {@link Command} implementation, as that one knows what to fire away on CVS. The runner is instantiated with a
 104  
    * <code>location</code> and a <code>manifest</code>. The location must be a <code>CVSLocationImpl</code> instance,
 105  
    * reprenting a CVS repository. The manifest is required because it determines the base point from where CVS commands
 106  
    * will be run; modules are checked out in a directory structure, relative to
 107  
    * {@link nl.toolforge.karma.core.manifest.Manifest#getModuleBaseDirectory()}.
 108  
    *
 109  340
    * @param location       A <code>Location</code> instance (typically a <code>CVSLocationImpl</code> instance), containing
 110  
    *                       the location and connection details of the CVS repository.
 111  340
    * @throws CVSException  <code>AUTHENTICATION_ERROR</code> is thrown when <code>location</code> cannot be authenticated.
 112  
    * @throws nl.toolforge.karma.core.vc.AuthenticationException If the location cannot be authenticated.
 113  369
    */
 114  203
   public CVSRunner(Location location) throws CVSException, nl.toolforge.karma.core.vc.AuthenticationException {
 115  261
 
 116  203
     if (location == null) {
 117  572
       throw new CVSException(VersionControlException.MISSING_LOCATION);
 118  
     }
 119  29
 
 120  203
     CVSRepository cvsLocation = null;
 121  601
     try {
 122  572
       cvsLocation = ((CVSRepository) location);
 123  435
       setLocation(cvsLocation);
 124  572
     } catch (ClassCastException e) {
 125  340
       logger.error("Wrong type for location. Should be CVSRepository.", e);
 126  29
       throw new KarmaRuntimeException("Wrong type for location. Should be CVSRepository.", e);
 127  203
     }
 128  232
 
 129  
     //If we don't have a
 130  369
     //
 131  232
     Authenticator a = Authenticators.getAuthenticator(cvsLocation.getAuthenticatorKey());
 132  775
     cvsLocation.setUsername(a.getUsername());
 133  601
 
 134  543
     if (cvsLocation.getProtocol().equals(CVSRepository.EXT)) {
 135  232
 
 136  0
       isExt = true;
 137  440
 
 138  0
       this.listener = new LogParser();
 139  
 
 140  
     } else {
 141  29
 
 142  203
       this.listener = new CVSResponseAdapter();
 143  261
 
 144  232
       connection = ConnectionFactory.getConnection(cvsLocation.getCVSRoot());
 145  435
       if (connection instanceof PServerConnection) {
 146  402
         ((PServerConnection) connection).setEncodedPassword(a.getPassword());
 147  170
       }
 148  29
 
 149  232
       logger.debug("CVSRunner using CVSROOT : " + cvsLocation.getCVSRoot());
 150  775
       globalOptions.setCVSRoot(cvsLocation.getCVSRoot());
 151  572
     }
 152  
 
 153  0
     // The default ...
 154  29
     //
 155  203
   }
 156  232
 
 157  29
   private void setLocation(CVSRepository location) {
 158  232
     this.location = location;
 159  435
   }
 160  232
 
 161  40
   private CVSRepository getLocation() {
 162  0
     return location;
 163  
   }
 164  
 
 165  39
   private Connection getConnection() {
 166  378
     return connection;
 167  472
   }
 168  40
 
 169  40
   /**
 170  40
    * Assigns a CommandResponse instance to the runner to optionally promote interactivity.
 171  40
    *
 172  
    * @param response A - possibly <code>null</code> response instance.
 173  40
    */
 174  57
   public void setCommandResponse(CommandResponse response) {
 175  136
     listener = new CVSResponseAdapter(response);
 176  255
   }
 177  136
 
 178  
   /**
 179  
    * Checks if the module is located in CVS within a subdirectory (or subdirs, any amount is possible). If so, the
 180  
    * module-name is prefixed with that offset.
 181  
    *
 182  
    * @param module
 183  
    * @return        The module-name, or - when applicable - the module-name prefixed with the value of
 184  
    *                {@link VersionControlSystem#getModuleOffset()}.
 185  
    */
 186  
   private String getModuleOffset(Module module) {
 187  
 
 188  
     // todo when modules from different locations have the same name, they should be given a namespace in front
 189  
     // of the module.
 190  29
 
 191  232
     if (((VersionControlSystem) module.getLocation()).getModuleOffset() == null) {
 192  435
       return module.getName();
 193  232
     } else {
 194  0
       return ((VersionControlSystem) module.getLocation()).getModuleOffset() + "/" + module.getName();
 195  0
     }
 196  
   }
 197  
 
 198  140
   public void commit(File file) throws VersionControlException {
 199  140
 
 200  
     // todo why not use the add()-method ????
 201  4
 
 202  0
     StandardAdminHandler adminHandler = new StandardAdminHandler();
 203  32
 
 204  0
     boolean isEntry = false;
 205  140
 
 206  
     try {
 207  4
       isEntry = (adminHandler.getEntry(file) != null);
 208  144
     } catch (IOException e) {
 209  36
       isEntry = false;
 210  176
     }
 211  36
 
 212  172
     if (isEntry) {
 213  176
 
 214  144
       CommitCommand commitCommand = new CommitCommand();
 215  32
       commitCommand.setFiles(new File[]{file});
 216  172
       commitCommand.setMessage("<undocumented> File committed by Karma");
 217  140
 
 218  0
       executeOnCVS(commitCommand, file.getParentFile(), null);
 219  140
 
 220  
     } else {
 221  0
       AddCommand addCommand = new AddCommand();
 222  0
       addCommand.setFiles(new File[]{file});
 223  
 
 224  0
       executeOnCVS(addCommand, file.getParentFile(), null);
 225  
 
 226  140
       CommitCommand commitCommand = new CommitCommand();
 227  0
       commitCommand.setFiles(new File[]{file});
 228  0
       commitCommand.setMessage("<undocumented> File committed by Karma");
 229  0
 
 230  0
       executeOnCVS(commitCommand, file.getParentFile(), null);
 231  
     }
 232  
 
 233  0
   }
 234  140
 
 235  
   public void addModule(Module module, String comment) throws CVSException {
 236  140
 
 237  
     // Step 1 : check if the module doesn't yet exist
 238  14
     //
 239  182
     if (existsInRepository(module)) {
 240  112
       throw new CVSException(CVSException.MODULE_EXISTS_IN_REPOSITORY, class="keyword">new Object[]{module.getName(), module.getLocation().getId()});
 241  112
     }
 242  
 
 243  
     // Step 2 : import the module, including its full structure.
 244  
     //
 245  42
     ImportCommand importCommand = new ImportCommand();
 246  28
     importCommand.setModule(getModuleOffset(module));
 247  140
     importCommand.setLogMessage("Module " + module.getName() + " created automatically by Karma on " + new Date().toString());
 248  42
     importCommand.setVendorTag("Karma");
 249  28
     importCommand.setReleaseTag("MAINLINE_0-0");
 250  126
 
 251  28
     executeOnCVS(importCommand, module.getBaseDir(), null);
 252  154
   }
 253  14
 
 254  126
   /**
 255  112
    * Performs the <code>cvs checkout [-r &lt;symbolic-name&gt;] &lt;module&gt;</code>command for a module.
 256  126
    * <code>version</code> is used when not <code>null</code> to checkout a module with a symbolic name.
 257  14
    *
 258  126
    * @param module        The module to check out.
 259  112
    * @param version       The version number for the module to check out.
 260  146
    * @throws CVSException With errorcodes <code>NO_SUCH_MODULE_IN_REPOSITORY</code> when the module does not exist
 261  42
    *                      in the repository and <code>INVALID_SYMBOLIC_NAME</code>, when the version does not exists for the module.
 262  90
    */
 263  62
   public void checkout(Module module, Version version) throws CVSException {
 264  20
 
 265  0
     checkout(module, null, version);
 266  20
   }
 267  34
 
 268  20
 
 269  70
   public void checkout(Module module) throws CVSException {
 270  160
     checkout(module, null, class="keyword">null);
 271  118
   }
 272  20
 
 273  20
   /**
 274  20
    * See {@link #checkout(Module, Version)}. This method defaults to the HEAD of the development branch at hand.
 275  56
    *
 276  42
    * @param module          The module to check out.
 277  84
    * @param developmentLine The development line or <code>null</code> when the TRUNK is the context line.
 278  112
    * @throws CVSException   With errorcodes <code>NO_SUCH_MODULE_IN_REPOSITORY</code> when the module does not exist
 279  0
    *                        in the repository and <code>INVALID_SYMBOLIC_NAME</code>, when the version does not exists
 280  84
    *                        for the module.
 281  42
    */
 282  20
   public void checkout(Module module, DevelopmentLine developmentLine, Version version) throws CVSException {
 283  70
 
 284  10
     //todo: proper exception handling
 285  10
     try {
 286  98
       MyFileUtils.makeWriteable(module.getBaseDir(), true);
 287  10
     } catch (Exception e) {
 288  0
       logger.error("Exception when making module writeable just before checking it out.", e);
 289  98
     }
 290  
 
 291  98
     boolean readonly = false;
 292  0
 
 293  98
     Map arguments = new Hashtable();
 294  98
     arguments.put("MODULE", module.getName());
 295  143
     arguments.put("REPOSITORY", module.getLocation().getId());
 296  45
 
 297  173
     CheckoutCommand checkoutCommand = new CheckoutCommand();
 298  218
     checkoutCommand.setModule(getModuleOffset(module));
 299  143
     checkoutCommand.setCheckoutDirectory(module.getName()); // Flatten to module name as the checkoutdir.
 300  218
     checkoutCommand.setPruneDirectories(true); //-P
 301  122
 
 302  218
     if ((version != null) || (developmentLine != class="keyword">null)) {
 303  122
       checkoutCommand.setCheckoutByRevision(Utils.createSymbolicName(module, developmentLine, version).getSymbolicName());
 304  77
       if (version != null) {
 305  77
         arguments.put("VERSION", version.toString());
 306  75
         readonly = true;
 307  77
       }
 308  77
     } else {
 309  205
       checkoutCommand.setResetStickyOnes(true);
 310  30
     }
 311  2
 
 312  2
     // The checkout directory for a module has to be relative to
 313  2
 
 314  130
     executeOnCVS(checkoutCommand, module.getBaseDir().getParentFile(), arguments);
 315  32
 
 316  98
     updateModuleHistoryXml(module);
 317  36
 
 318  98
     if (readonly) {
 319  16
       MyFileUtils.makeReadOnly(module.getBaseDir());
 320  6
     }
 321  144
   }
 322  40
 
 323  78
   private void updateModuleHistoryXml(Module module) throws CVSException {
 324  141
     logger.debug("Updating history.xml to HEAD.");
 325  122
     UpdateCommand command = new UpdateCommand();
 326  41
     try {
 327  121
       ModuleHistoryFactory factory = ModuleHistoryFactory.getInstance(module.getBaseDir());
 328  142
       File historyLocation = factory.getModuleHistory(module).getHistoryLocation();
 329  121
       command.setFiles(new File[]{historyLocation});
 330  151
       command.setResetStickyOnes(true);  //-A
 331  121
       executeOnCVS(command, module.getBaseDir(), null);
 332  115
       logger.debug("Done updating history.xml to HEAD.");
 333  10
     } catch (ModuleHistoryException mhe) {
 334  30
       logger.error("ModuleHistoryException when updating module history to HEAD", mhe);
 335  135
     }
 336  105
   }
 337  
 
 338  30
 
 339  6
   /**
 340  3
    * @see #update(Module, DevelopmentLine, Version)
 341  40
    */
 342  8
   public void update(Module module) throws CVSException {
 343  3
     update(module, null);
 344  5
   }
 345  8
 
 346  60
   /**
 347  38
    * @see #update(Module, DevelopmentLine, Version)
 348  3
    */
 349  0
   public void update(Module module, Version version) throws CVSException {
 350  47
     update(module, null, version);
 351  10
   }
 352  0
 
 353  0
   /**
 354  30
    * For a module, the <code>cvs -q update -d -r &lt;symbolic-name&gt;</code> command is executed. Note that empty
 355  3
    * directories are not pruned.
 356  3
    *
 357  
    * @param module          The module to update.
 358  33
    * @param developmentLine The development line or <code>null</code> when the TRUNK is the context line.
 359  30
    * @param version         The version of the module or <code>null</code> when no specific version applies.
 360  90
    */
 361  60
   public void update(Module module, DevelopmentLine developmentLine, Version version) throws CVSException {
 362  63
 
 363  63
     //todo: proper exception handling
 364  75
     try {
 365  26
       MyFileUtils.makeWriteable(module.getBaseDir(), true);
 366  15
     } catch (Exception e) {
 367  27
       logger.error("Exception when making module writeable just before updating it.", e);
 368  53
     }
 369  48
 
 370  29
     boolean readonly = false;
 371  3
 
 372  23
     Map arguments = new Hashtable();
 373  23
     arguments.put("MODULE", module.getName());
 374  59
     arguments.put("REPOSITORY", module.getLocation().getId());
 375  27
 
 376  17
     UpdateCommand updateCommand = new UpdateCommand();
 377  29
     updateCommand.setRecursive(true);
 378  44
     updateCommand.setBuildDirectories(true); //-d
 379  56
     updateCommand.setPruneDirectories(false); //-P
 380  39
 
 381  47
     if (version != null || module.hasPatchLine()) {
 382  71
       updateCommand.setUpdateByRevision(Utils.createSymbolicName(module, developmentLine, version).getSymbolicName());
 383  74
       if (version != null) {
 384  38
         arguments.put("VERSION", version.toString());
 385  14
         readonly = true;
 386  24
       }
 387  16
     } else {
 388  27
       updateCommand.setResetStickyOnes(true);
 389  10
     }
 390  25
 
 391  13
     // todo add data to the exception. this sort of business logic should be here, not in CVSResponseAdapter.
 392  9
     //
 393  33
     executeOnCVS(updateCommand, module.getBaseDir(), arguments);
 394  15
 
 395  35
     updateModuleHistoryXml(module);
 396  19
 
 397  7
     if (readonly) {
 398  22
       MyFileUtils.makeReadOnly(module.getBaseDir());
 399  12
     }
 400  10
   }
 401  24
 
 402  6
   public void add(Module module, File[] files, File[] dirs) throws CVSException {
 403  6
 
 404  24
     String[] f = (files == null ? new String[0] : class="keyword">new String[files.length]);
 405  15
     String[] d = (dirs == null ? new String[0] : class="keyword">new String[dirs.length]);
 406  30
 
 407  15
     for (int i = 0; i < f.length; i++) {
 408  9
       f[i] = files[i].getPath();
 409  3
     }
 410  18
     for (int i = 0; i < d.length; i++) {
 411  0
       d[i] = dirs[i].getPath();
 412  9
     }
 413  
 
 414  15
     add(module, f, d);
 415  3
   }
 416  9
 
 417  9
   public void add(Module module, String[] files, String[] dirs) throws CVSException {
 418  42
 
 419  57
     files = (files == null ? new String[] {} : files);
 420  87
     dirs = (dirs == null ? new String[] {} : dirs);
 421  51
 
 422  69
     Map arguments = new Hashtable();
 423  54
     arguments.put("MODULE", module.getName());
 424  43
 
 425  10
     // Step 1 : Add the file to the CVS repository
 426  19
     //
 427  40
     AddCommand addCommand = new AddCommand();
 428  37
     addCommand.setMessage("Initial checkin in repository by Karma.");
 429  25
 
 430  32
     File modulePath = module.getBaseDir();
 431  1
 
 432  10
     // Create temp files
 433  
     //
 434  37
     Collection cvsFilesCollection = new ArrayList();
 435  21
     int i = 0;
 436  52
     for (i=0; i < files.length; i++) {
 437  31
       File fileToAdd = new File(modulePath, files[i]);
 438  24
 
 439  96
       if (!fileToAdd.exists()) {
 440  24
         try {
 441  30
           File dir = new File(modulePath, files[i]).getParentFile();
 442  15
 
 443  36
           if (dir.mkdirs()) {
 444  60
             cvsFilesCollection.add(dir);
 445  63
           }
 446  60
 
 447  29
           fileToAdd.createNewFile();
 448  84
           logger.debug("Created file " + files[i] + " for module " + module.getName() + ".");
 449  65
         } catch (IOException e) {
 450  5
           throw new KarmaRuntimeException("Error while creating module layout for module " + module.getName());
 451  24
         }
 452  60
       }
 453  8
 
 454  24
       cvsFilesCollection.add(fileToAdd);
 455  15
     }
 456  5
 
 457  
     // Create temp directories
 458  
     //
 459  42
     for (i=0; i < dirs.length; i++) {
 460  21
       File dirToAdd = new File(modulePath, dirs[i]);
 461  0
 
 462  
       //try to create the dirs
 463  21
       if (!dirToAdd.mkdirs() && !dirToAdd.exists()) {
 464  0
         //failed to create the dirs and they do not exist yet.
 465  1
         throw new KarmaRuntimeException("Error while creating module layout for module " + module.getName());
 466  1
       }
 467  82
       logger.debug("Created directory " + dirs[i] + " for module " + module.getName() + ".");
 468  51
 
 469  50
       // Ensure that all directories are added correctly
 470  1
       //
 471  22
       StringTokenizer tokenizer = new StringTokenizer(dirs[i], "/");
 472  21
       String base = "";
 473  63
       while (tokenizer.hasMoreTokens()) {
 474  42
         String subDir = tokenizer.nextToken();
 475  42
         base += subDir;
 476  42
         cvsFilesCollection.add(new File(modulePath, base));
 477  42
         base += "/";
 478  
       }
 479  
     }
 480  1
 
 481  22
     File[] cvsFiles = (File[]) cvsFilesCollection.toArray(new File[cvsFilesCollection.size()]);
 482  25
     addCommand.setFiles(cvsFiles);
 483  13
 
 484  19
     // A file is added against a module, thus the contextDirectory is constructed based on the basePoint and the
 485  9
     // modules' name.
 486  15
     //
 487  30
     executeOnCVS(addCommand, module.getBaseDir(), arguments);
 488  3
 
 489  5
     // Step 2 : Commit the file to the CVS repository
 490  5
     //
 491  32
     CommitCommand commitCommand = new CommitCommand();
 492  31
     commitCommand.setFiles(cvsFiles);
 493  21
     commitCommand.setMessage("File added automatically by Karma.");
 494  1
 
 495  21
     executeOnCVS(commitCommand, module.getBaseDir(), arguments);
 496  21
   }
 497  3
 
 498  3
   private void commit(DevelopmentLine developmentLine, Module module, File file, String message) throws CVSException {
 499  8
 
 500  12
     CommitCommand commitCommand = new CommitCommand();
 501  108
 
 502  10
     commitCommand.setFiles(new File[]{file});
 503  12
     if (developmentLine != null) {
 504  8
       commitCommand.setToRevisionOrBranch(developmentLine.getName());
 505  
     }
 506  112
     commitCommand.setMessage(message);
 507  100
 
 508  110
     executeOnCVS(commitCommand, module.getBaseDir(), null);
 509  7
   }
 510  55
 
 511  53
   public void promote(Module module, String comment, Version version) throws CVSException {
 512  50
 
 513  55
     try {
 514  
       //Add an event to the module history.
 515  0
       String author = ((CVSRepository) module.getLocation()).getUsername();
 516  0
       addModuleHistoryEvent(module, ModuleHistoryEvent.PROMOTE_MODULE_EVENT, version, new Date(), author, comment);
 517  
 
 518  0
       tag(module, version);
 519  0
     } catch (ModuleHistoryException mhe) {
 520  0
       logger.error("Writing the history.xml failed", mhe);
 521  0
       throw new CVSException(CVSException.MODULE_HISTORY_ERROR, class="keyword">new Object[]{mhe.getMessage()});
 522  60
     }
 523  0
   }
 524  3
 
 525  0
   private void tag(Module module, Version version) throws CVSException {
 526  65
     if (hasVersion(module, version)) {
 527  10
       throw new CVSException(VersionControlException.DUPLICATE_VERSION, class="keyword">new Object[]{module.getName(), version.getVersionNumber()});
 528  
     }
 529  10
     tag(module, Utils.createSymbolicName(module, version), false);
 530  20
   }
 531  
 
 532  
 //
 533  10
 // Private for the time being
 534  
 //
 535  
   private void tag(Module module, SymbolicName symbolicName, boolean branch) throws CVSException {
 536  
 
 537  7
     TagCommand tagCommand = new TagCommand();
 538  7
     tagCommand.setRecursive(true);
 539  7
     tagCommand.setTag(symbolicName.getSymbolicName());
 540  10
     tagCommand.setMakeBranchTag(branch);
 541  3
 
 542  12
     executeOnCVS(tagCommand, module.getBaseDir(), null);
 543  15
   }
 544  
 
 545  5
   /**
 546  0
    * Provide log information on a module. This method checks if the
 547  0
    * {@link #setCommandResponse(nl.toolforge.karma.core.cmd.CommandResponse)} method has been called, which results in
 548  443
    * this runner initializing the CVS API with the correct objects. This is a requirement for the Netbeans CVS API.
 549  3
    */
 550  5
   public LogInformation log(Module module) throws CVSException {
 551  5
 
 552  447
     Map arguments = new Hashtable();
 553  447
     arguments.put("MODULE", module.getName());
 554  7
     arguments.put("REPOSITORY", module.getLocation().getId());
 555  440
 
 556  7
     RlogCommand logCommand = new RlogCommand();
 557  7
     logCommand.setModule(getModuleOffset(module) + "/" + Module.MODULE_DESCRIPTOR);
 558  36
     logCommand.setRecursive(false);
 559  2
 
 560  55
     executeOnCVS(logCommand, MyFileUtils.getSystemTempDirectory(), arguments);
 561  440
 
 562  440
     // Another hook into ext support.
 563  472
     //
 564  35
     if (isExt) {
 565  520
       return ((LogParser) this.listener).getLogInformation();
 566  162
     } else {
 567  180
       return ((CVSResponseAdapter) this.listener).getLogInformation();
 568  125
     }
 569  150
   }
 570  150
 
 571  25
   /*
 572  25
 
 573  
   DISABLED, DO NOT DELETE, EXPERIMENTAL
 574  1
 
 575  1
   public LogInformation log(Manifest manifest) throws CVSException {
 576  
 
 577  1
 
 578  
   RlogCommand logCommand = new RlogCommand();
 579  5
 
 580  110
   String[] modules = new String[manifest.getAllModules().size()];
 581  445
   int j = 0;
 582  1
   for (Iterator i = manifest.getAllModules().values().iterator(); i.hasNext();) {
 583  4
   modules[j] = getModuleOffset((Module) i.next()) + "/" + Module.MODULE_DESCRIPTOR;
 584  883
   j++;
 585  335
   }
 586  338
 
 587  6
   logCommand.setModules(modules);
 588  5
 
 589  10
   executeOnCVS(logCommand, new File("."), null);
 590  3
 
 591  
   // Another hook into ext support.
 592  15
   //
 593  
   return ((CVSResponseAdapter) this.listener).getLogInformation();
 594  
   }
 595  2
 
 596  2
   */
 597  10
 
 598  12
 
 599  10
   /**
 600  
    * Checks if the module has a CVS branch tag <code>module.getPatchLine().getName()</code> attached.
 601  5
    *
 602  5
    * @param module The module for which the patch line should be checked.
 603  7
    * @return <code>true</code> of the module has a patch line attached in the CVS repository, <code>false</code>
 604  7
    *         otherwise.
 605  
    */
 606  162
   public boolean hasPatchLine(Module module) {
 607  
     try {
 608  275
       return hasSymbolicName(module, new CVSTag(class="keyword">new PatchLine(module.getVersion()).getName()));
 609  0
     } catch (CVSException c) {
 610  0
       return false;
 611  10
     }
 612  10
   }
 613  31
 
 614  172
   /**
 615  10
    * Creates a patchline for the module, given the modules' current version.
 616  280
    *
 617  11
    * @param module The module.
 618  193
    * @throws CVSException When an error occurred during the creation process.
 619  192
    */
 620  301
   public void createPatchLine(Module module) throws CVSException {
 621  444
     try {
 622  10
       // Add an event to the module history.
 623  290
       //
 624  20
       String author = ((CVSRepository) module.getLocation()).getUsername();
 625  21
       tag(module, new CVSTag(module.getPatchLine().getName()), true);
 626  
 
 627  167
       addModuleHistoryEvent(module, ModuleHistoryEvent.CREATE_PATCH_LINE_EVENT, module.getVersion(), new Date(), author, "Patch line created by Karma");
 628  162
 
 629  432
     } catch (ModuleHistoryException mhe) {
 630  270
       logger.error("Writing the history.xml failed", mhe);
 631  436
       throw new CVSException(CVSException.MODULE_HISTORY_ERROR, class="keyword">new Object[]{mhe.getMessage()});
 632  29
     }
 633  293
   }
 634  50
 
 635  48
   /**
 636  48
    * A check if a module exists is done by trying to checkout the modules' <code>module.info</code> file in a temporary
 637  30
    * location. If that succeeds, apparently the module exists in that location and we have a <code>true</code> to
 638  32
    * return.
 639  2
    */
 640  39
   public boolean existsInRepository(Module module) {
 641  2
 
 642  54
     if (module == null) {
 643  0
       return false;
 644  
     }
 645  2
 
 646  18
     try {
 647  212
       RlogCommand logCommand = new RlogCommand();
 648  119
       logCommand.setModule(getModuleOffset(module));
 649  270
 
 650  374
       executeOnCVS(logCommand, MyFileUtils.getSystemTempDirectory(), null);
 651  169
     } catch (CVSException e) {
 652  604
       return false;
 653  448
     }
 654  25
     return true;
 655  279
   }
 656  
 
 657  
   private boolean hasVersion(Module module, Version version) throws CVSException {
 658  0
     return hasSymbolicName(module, Utils.createSymbolicName(module, version));
 659  
   }
 660  
 
 661  147
   private boolean hasSymbolicName(Module module, SymbolicName symbolicName) throws CVSException {
 662  39
 
 663  44
     if (symbolicName == null) {
 664  0
       return false;
 665  39
     }
 666  6
 
 667  11
     LogInformation logInfo = log(module);
 668  11
     List symbolicNames = new ArrayList();
 669  114
 
 670  11
     for (Iterator i = logInfo.getAllSymbolicNames().iterator(); i.hasNext();) {
 671  10
       symbolicNames.add(((LogInformation.SymName) i.next()).getName());
 672  
     }
 673  108
 
 674  113
     return symbolicNames.contains(symbolicName.getSymbolicName());
 675  
   }
 676  111
 
 677  3
   /**
 678  8
    * Runs a CVS command on the repository (through the Netbeans API). When a co
 679  8
    *
 680  14
    * @param command          A command object, representing the CVS command.
 681  47
    * @param contextDirectory The directory from where the command should be run.
 682  116
    * @param args             Arguments that can be passed to the CVSRuntimeException thrown by the CVSListener.
 683  116
    *
 684  194
    * @throws CVSException    When CVS has reported an error through its listener.
 685  41
    */
 686  113
   private void executeOnCVS(org.netbeans.lib.cvsclient.command.Command command,
 687  50
                             File contextDirectory, Map args) throws CVSException {
 688  15
     // Switch ...
 689  15
     //
 690  287
     if (isExt) {
 691  20
 
 692  3
       ExtClient client = new ExtClient();
 693  5
       client.addCVSListener((LogParser) listener);
 694  5
       client.runCommand(command, contextDirectory, getLocation());
 695  3
 
 696  
     } else {
 697  5
 
 698  270
       if (contextDirectory == null) {
 699  0
         throw new NullPointerException("Context directory cannot be null.");
 700  
       }
 701  15
 
 702  378
       CVSException exception = null;
 703  221
 
 704  54
       int retries = 0;
 705  432
 
 706  156
       while (retries < 3) {
 707  
 
 708  160
         Client client = new Client(getConnection(), class="keyword">new StandardAdminHandler());
 709  54
         client.setLocalPath(contextDirectory.getPath());
 710  
 
 711  57
         if (retries == 0) {
 712  271
           logger.debug("Running CVS command : '" + command.getCVSCommand() + "' in " + client.getLocalPath());
 713  6
         }
 714  217
 
 715  217
         try {
 716  217
           // A CVSResponseAdapter is registered as a listener for the response from CVS. This one adapts to Karma
 717  1
           // specific stuff.
 718  193
           //
 719  1
 
 720  271
           long start = System.currentTimeMillis();
 721  25
 
 722  79
           ((CVSResponseAdapter) listener).setArguments(args);
 723  78
           client.getEventManager().addCVSListener(listener);
 724  79
           client.executeCommand(command, globalOptions);
 725  25
 
 726  48
           logger.debug("CVS command finished in " + (System.currentTimeMillis() - start) + " ms.");
 727  
 
 728  0
           // Apparently, were done succesfully.
 729  0
           //
 730  48
           break;
 731  2
 
 732  9
         } catch (CommandException e) {
 733  8
           logger.error(e.getMessage(), e);
 734  8
           if (e.getUnderlyingException() instanceof CVSRuntimeException) {
 735  32
             ErrorCode code = ((CVSRuntimeException) e.getUnderlyingException()).getErrorCode();
 736  224
             Object[] messageArgs = ((CVSRuntimeException) e.getUnderlyingException()).getMessageArguments();
 737  8
             throw new CVSException(code, messageArgs);
 738  2
           } else {
 739  434
             throw new CVSException(CVSException.INTERNAL_ERROR, class="keyword">new Object[]{globalOptions.getCVSRoot()});
 740  194
           }
 741  0
         } catch (AuthenticationException e) {
 742  192
           // Over here, we have implemented a retry-mechanism.
 743  2
           //
 744  2
           if (retries == 3) {
 745  0
             if (e.getUnderlyingThrowable() instanceof IOException) {
 746  2
               logger.error("Cannot retry any longer.", e);
 747  2
             }
 748  0
             exception = new CVSException(CVSException.INTERNAL_ERROR, class="keyword">new Object[]{client.getGlobalOptions().getCVSRoot()});
 749  
           } else {
 750  2
             logger.info("Retrying CVS command : '" + command.getCVSCommand() + "' in " + client.getLocalPath());
 751  
 //            logger.error(e.getMessage(), e);
 752  
           }
 753  0
           retries ++;
 754  0
         } finally {
 755  
           // See the static block in this class and corresponding documentation.
 756  2
           //
 757  6
           try {
 758  54
             client.getConnection().close();
 759  0
           } catch (IOException e) {
 760  0
             throw new CVSException(CVSException.INTERNAL_ERROR, class="keyword">new Object[]{globalOptions.getCVSRoot()});
 761  112
           }
 762  0
         }
 763  
       }
 764  
 
 765  52
       if (exception != null) {
 766  6
         throw exception;
 767  4
       }
 768  4
     }
 769  52
   }
 770  4
 
 771  4
   /**
 772  4
    * Helper method that add a new event to a module's history. When the history does not
 773  4
    * exist yet (in case of a new module) it is newly created. When the history does exist
 774  4
    * the event is added to the history.
 775  
    *
 776  
    * @param module                  The module involved.
 777  4
    * @param eventType               The type of {@link ModuleHistoryEvent}.
 778  4
    * @param version                 The version that the module is promoted to.
 779  
    * @param datetime                The timestamp.
 780  4
    * @param author                  The authentication of the person who has triggered the event.
 781  4
    * @param comment                 The (optional) comment the author has given.
 782  
    * @throws CVSException           Thrown in case something goes wrong with CVS
 783  
    */
 784  4
   private void addModuleHistoryEvent(
 785  
       Module module,
 786  
       String eventType,
 787  
       Version version,
 788  
       Date datetime,
 789  
       String author,
 790  4
       String comment) throws CVSException, ModuleHistoryException
 791  
   {
 792  1
     ModuleHistoryFactory factory = ModuleHistoryFactory.getInstance(module.getBaseDir());
 793  1
     ModuleHistory history = factory.getModuleHistory(module);
 794  1
     if (history != null) {
 795  9
       ModuleHistoryEvent event = new ModuleHistoryEvent();
 796  1
       event.setType(eventType);
 797  1
       event.setVersion(version);
 798  1
       event.setDatetime(datetime);
 799  1
       event.setAuthor(author);
 800  5
       event.setComment(comment);
 801  1
       history.addEvent(event);
 802  
 
 803  
       try {
 804  1
         MyFileUtils.makeWriteable(new File(module.getBaseDir(), "CVS"), false);
 805  1
         if (history.getHistoryLocation().exists()) {
 806  
           //history already exists. commit changes.
 807  1
           MyFileUtils.makeWriteable(history.getHistoryLocation(), false);
 808  1
           history.save();
 809  
 
 810  
           //development line is null, since the history.xml is always committed in the HEAD.
 811  1
           commit(null, module, new File(module.getBaseDir(), ModuleHistory.MODULE_HISTORY_FILE_NAME), "History updated by Karma");
 812  
         } else {
 813  
           //history did not exist yet. add to CVS and commit it.
 814  0
           history.save();
 815  0
           add(module, new String[]{history.getHistoryLocation().getName()}, null);
 816  
         }
 817  1
         MyFileUtils.makeReadOnly(module.getBaseDir());
 818  0
       } catch (IOException ioe) {
 819  0
         logger.error("Error when making history.xml readonly/writeable", ioe);
 820  0
       } catch (InterruptedException ie) {
 821  0
         logger.error("Error when making history.xml readonly/writeable", ie);
 822  2
       }
 823  
     } else {
 824  
       //history wel null. EN NU?! todo
 825  0
       throw new OutOfTheBlueException("If this happens something rrreally went wrong with the history");
 826  
     }
 827  1
   }
 828  
 
 829  
 }
 830  
 

This report is generated by jcoverage, Maven and Maven JCoverage Plugin.