1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.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
86
87 System.setProperty("javacvs.multiple_commands_warning", "false");
88 }
89
90 private CVSListener listener = null;
91
92 private static Log logger = LogFactory.getLog(CVSRunner.class);
93
94 private GlobalOptions globalOptions = new GlobalOptions();
95 private Connection connection = null;
96
97 private CVSRepository location = null;
98
99 private boolean isExt = false;
100
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 * @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 * @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 */
114 public CVSRunner(Location location) throws CVSException, nl.toolforge.karma.core.vc.AuthenticationException {
115
116 if (location == null) {
117 throw new CVSException(VersionControlException.MISSING_LOCATION);
118 }
119
120 CVSRepository cvsLocation = null;
121 try {
122 cvsLocation = ((CVSRepository) location);
123 setLocation(cvsLocation);
124 } catch (ClassCastException e) {
125 logger.error("Wrong type for location. Should be CVSRepository.", e);
126 throw new KarmaRuntimeException("Wrong type for location. Should be CVSRepository.", e);
127 }
128
129
130
131 Authenticator a = Authenticators.getAuthenticator(cvsLocation.getAuthenticatorKey());
132 cvsLocation.setUsername(a.getUsername());
133
134 if (cvsLocation.getProtocol().equals(CVSRepository.EXT)) {
135
136 isExt = true;
137
138 this.listener = new LogParser();
139
140 } else {
141
142 this.listener = new CVSResponseAdapter();
143
144 connection = ConnectionFactory.getConnection(cvsLocation.getCVSRoot());
145 if (connection instanceof PServerConnection) {
146 ((PServerConnection) connection).setEncodedPassword(a.getPassword());
147 }
148
149 logger.debug("CVSRunner using CVSROOT : " + cvsLocation.getCVSRoot());
150 globalOptions.setCVSRoot(cvsLocation.getCVSRoot());
151 }
152
153
154
155 }
156
157 private void setLocation(CVSRepository location) {
158 this.location = location;
159 }
160
161 private CVSRepository getLocation() {
162 return location;
163 }
164
165 private Connection getConnection() {
166 return connection;
167 }
168
169 /***
170 * Assigns a CommandResponse instance to the runner to optionally promote interactivity.
171 *
172 * @param response A - possibly <code>null</code> response instance.
173 */
174 public void setCommandResponse(CommandResponse response) {
175 listener = new CVSResponseAdapter(response);
176 }
177
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
189
190
191 if (((VersionControlSystem) module.getLocation()).getModuleOffset() == null) {
192 return module.getName();
193 } else {
194 return ((VersionControlSystem) module.getLocation()).getModuleOffset() + "/" + module.getName();
195 }
196 }
197
198 public void commit(File file) throws VersionControlException {
199
200
201
202 StandardAdminHandler adminHandler = new StandardAdminHandler();
203
204 boolean isEntry = false;
205
206 try {
207 isEntry = (adminHandler.getEntry(file) != null);
208 } catch (IOException e) {
209 isEntry = false;
210 }
211
212 if (isEntry) {
213
214 CommitCommand commitCommand = new CommitCommand();
215 commitCommand.setFiles(new File[]{file});
216 commitCommand.setMessage("<undocumented> File committed by Karma");
217
218 executeOnCVS(commitCommand, file.getParentFile(), null);
219
220 } else {
221 AddCommand addCommand = new AddCommand();
222 addCommand.setFiles(new File[]{file});
223
224 executeOnCVS(addCommand, file.getParentFile(), null);
225
226 CommitCommand commitCommand = new CommitCommand();
227 commitCommand.setFiles(new File[]{file});
228 commitCommand.setMessage("<undocumented> File committed by Karma");
229
230 executeOnCVS(commitCommand, file.getParentFile(), null);
231 }
232
233 }
234
235 public void addModule(Module module, String comment) throws CVSException {
236
237
238
239 if (existsInRepository(module)) {
240 throw new CVSException(CVSException.MODULE_EXISTS_IN_REPOSITORY, new Object[]{module.getName(), module.getLocation().getId()});
241 }
242
243
244
245 ImportCommand importCommand = new ImportCommand();
246 importCommand.setModule(getModuleOffset(module));
247 importCommand.setLogMessage("Module " + module.getName() + " created automatically by Karma on " + new Date().toString());
248 importCommand.setVendorTag("Karma");
249 importCommand.setReleaseTag("MAINLINE_0-0");
250
251 executeOnCVS(importCommand, module.getBaseDir(), null);
252 }
253
254 /***
255 * Performs the <code>cvs checkout [-r <symbolic-name>] <module></code>command for a module.
256 * <code>version</code> is used when not <code>null</code> to checkout a module with a symbolic name.
257 *
258 * @param module The module to check out.
259 * @param version The version number for the module to check out.
260 * @throws CVSException With errorcodes <code>NO_SUCH_MODULE_IN_REPOSITORY</code> when the module does not exist
261 * in the repository and <code>INVALID_SYMBOLIC_NAME</code>, when the version does not exists for the module.
262 */
263 public void checkout(Module module, Version version) throws CVSException {
264
265 checkout(module, null, version);
266 }
267
268
269 public void checkout(Module module) throws CVSException {
270 checkout(module, null, null);
271 }
272
273 /***
274 * See {@link #checkout(Module, Version)}. This method defaults to the HEAD of the development branch at hand.
275 *
276 * @param module The module to check out.
277 * @param developmentLine The development line or <code>null</code> when the TRUNK is the context line.
278 * @throws CVSException With errorcodes <code>NO_SUCH_MODULE_IN_REPOSITORY</code> when the module does not exist
279 * in the repository and <code>INVALID_SYMBOLIC_NAME</code>, when the version does not exists
280 * for the module.
281 */
282 public void checkout(Module module, DevelopmentLine developmentLine, Version version) throws CVSException {
283
284
285 try {
286 MyFileUtils.makeWriteable(module.getBaseDir(), true);
287 } catch (Exception e) {
288 logger.error("Exception when making module writeable just before checking it out.", e);
289 }
290
291 boolean readonly = false;
292
293 Map arguments = new Hashtable();
294 arguments.put("MODULE", module.getName());
295 arguments.put("REPOSITORY", module.getLocation().getId());
296
297 CheckoutCommand checkoutCommand = new CheckoutCommand();
298 checkoutCommand.setModule(getModuleOffset(module));
299 checkoutCommand.setCheckoutDirectory(module.getName());
300 checkoutCommand.setPruneDirectories(true);
301
302 if ((version != null) || (developmentLine != null)) {
303 checkoutCommand.setCheckoutByRevision(Utils.createSymbolicName(module, developmentLine, version).getSymbolicName());
304 if (version != null) {
305 arguments.put("VERSION", version.toString());
306 readonly = true;
307 }
308 } else {
309 checkoutCommand.setResetStickyOnes(true);
310 }
311
312
313
314 executeOnCVS(checkoutCommand, module.getBaseDir().getParentFile(), arguments);
315
316 updateModuleHistoryXml(module);
317
318 if (readonly) {
319 MyFileUtils.makeReadOnly(module.getBaseDir());
320 }
321 }
322
323 private void updateModuleHistoryXml(Module module) throws CVSException {
324 logger.debug("Updating history.xml to HEAD.");
325 UpdateCommand command = new UpdateCommand();
326 try {
327 ModuleHistoryFactory factory = ModuleHistoryFactory.getInstance(module.getBaseDir());
328 File historyLocation = factory.getModuleHistory(module).getHistoryLocation();
329 command.setFiles(new File[]{historyLocation});
330 command.setResetStickyOnes(true);
331 executeOnCVS(command, module.getBaseDir(), null);
332 logger.debug("Done updating history.xml to HEAD.");
333 } catch (ModuleHistoryException mhe) {
334 logger.error("ModuleHistoryException when updating module history to HEAD", mhe);
335 }
336 }
337
338
339 /***
340 * @see #update(Module, DevelopmentLine, Version)
341 */
342 public void update(Module module) throws CVSException {
343 update(module, null);
344 }
345
346 /***
347 * @see #update(Module, DevelopmentLine, Version)
348 */
349 public void update(Module module, Version version) throws CVSException {
350 update(module, null, version);
351 }
352
353 /***
354 * For a module, the <code>cvs -q update -d -r <symbolic-name></code> command is executed. Note that empty
355 * directories are not pruned.
356 *
357 * @param module The module to update.
358 * @param developmentLine The development line or <code>null</code> when the TRUNK is the context line.
359 * @param version The version of the module or <code>null</code> when no specific version applies.
360 */
361 public void update(Module module, DevelopmentLine developmentLine, Version version) throws CVSException {
362
363
364 try {
365 MyFileUtils.makeWriteable(module.getBaseDir(), true);
366 } catch (Exception e) {
367 logger.error("Exception when making module writeable just before updating it.", e);
368 }
369
370 boolean readonly = false;
371
372 Map arguments = new Hashtable();
373 arguments.put("MODULE", module.getName());
374 arguments.put("REPOSITORY", module.getLocation().getId());
375
376 UpdateCommand updateCommand = new UpdateCommand();
377 updateCommand.setRecursive(true);
378 updateCommand.setBuildDirectories(true);
379 updateCommand.setPruneDirectories(false);
380
381 if (version != null || module.hasPatchLine()) {
382 updateCommand.setUpdateByRevision(Utils.createSymbolicName(module, developmentLine, version).getSymbolicName());
383 if (version != null) {
384 arguments.put("VERSION", version.toString());
385 readonly = true;
386 }
387 } else {
388 updateCommand.setResetStickyOnes(true);
389 }
390
391
392
393 executeOnCVS(updateCommand, module.getBaseDir(), arguments);
394
395 updateModuleHistoryXml(module);
396
397 if (readonly) {
398 MyFileUtils.makeReadOnly(module.getBaseDir());
399 }
400 }
401
402 public void add(Module module, File[] files, File[] dirs) throws CVSException {
403
404 String[] f = (files == null ? new String[0] : new String[files.length]);
405 String[] d = (dirs == null ? new String[0] : new String[dirs.length]);
406
407 for (int i = 0; i < f.length; i++) {
408 f[i] = files[i].getPath();
409 }
410 for (int i = 0; i < d.length; i++) {
411 d[i] = dirs[i].getPath();
412 }
413
414 add(module, f, d);
415 }
416
417 public void add(Module module, String[] files, String[] dirs) throws CVSException {
418
419 files = (files == null ? new String[] {} : files);
420 dirs = (dirs == null ? new String[] {} : dirs);
421
422 Map arguments = new Hashtable();
423 arguments.put("MODULE", module.getName());
424
425
426
427 AddCommand addCommand = new AddCommand();
428 addCommand.setMessage("Initial checkin in repository by Karma.");
429
430 File modulePath = module.getBaseDir();
431
432
433
434 Collection cvsFilesCollection = new ArrayList();
435 int i = 0;
436 for (i=0; i < files.length; i++) {
437 File fileToAdd = new File(modulePath, files[i]);
438
439 if (!fileToAdd.exists()) {
440 try {
441 File dir = new File(modulePath, files[i]).getParentFile();
442
443 if (dir.mkdirs()) {
444 cvsFilesCollection.add(dir);
445 }
446
447 fileToAdd.createNewFile();
448 logger.debug("Created file " + files[i] + " for module " + module.getName() + ".");
449 } catch (IOException e) {
450 throw new KarmaRuntimeException("Error while creating module layout for module " + module.getName());
451 }
452 }
453
454 cvsFilesCollection.add(fileToAdd);
455 }
456
457
458
459 for (i=0; i < dirs.length; i++) {
460 File dirToAdd = new File(modulePath, dirs[i]);
461
462
463 if (!dirToAdd.mkdirs() && !dirToAdd.exists()) {
464
465 throw new KarmaRuntimeException("Error while creating module layout for module " + module.getName());
466 }
467 logger.debug("Created directory " + dirs[i] + " for module " + module.getName() + ".");
468
469
470
471 StringTokenizer tokenizer = new StringTokenizer(dirs[i], "/");
472 String base = "";
473 while (tokenizer.hasMoreTokens()) {
474 String subDir = tokenizer.nextToken();
475 base += subDir;
476 cvsFilesCollection.add(new File(modulePath, base));
477 base += "/";
478 }
479 }
480
481 File[] cvsFiles = (File[]) cvsFilesCollection.toArray(new File[cvsFilesCollection.size()]);
482 addCommand.setFiles(cvsFiles);
483
484
485
486
487 executeOnCVS(addCommand, module.getBaseDir(), arguments);
488
489
490
491 CommitCommand commitCommand = new CommitCommand();
492 commitCommand.setFiles(cvsFiles);
493 commitCommand.setMessage("File added automatically by Karma.");
494
495 executeOnCVS(commitCommand, module.getBaseDir(), arguments);
496 }
497
498 private void commit(DevelopmentLine developmentLine, Module module, File file, String message) throws CVSException {
499
500 CommitCommand commitCommand = new CommitCommand();
501
502 commitCommand.setFiles(new File[]{file});
503 if (developmentLine != null) {
504 commitCommand.setToRevisionOrBranch(developmentLine.getName());
505 }
506 commitCommand.setMessage(message);
507
508 executeOnCVS(commitCommand, module.getBaseDir(), null);
509 }
510
511 public void promote(Module module, String comment, Version version) throws CVSException {
512
513 try {
514
515 String author = ((CVSRepository) module.getLocation()).getUsername();
516 addModuleHistoryEvent(module, ModuleHistoryEvent.PROMOTE_MODULE_EVENT, version, new Date(), author, comment);
517
518 tag(module, version);
519 } catch (ModuleHistoryException mhe) {
520 logger.error("Writing the history.xml failed", mhe);
521 throw new CVSException(CVSException.MODULE_HISTORY_ERROR, new Object[]{mhe.getMessage()});
522 }
523 }
524
525 private void tag(Module module, Version version) throws CVSException {
526 if (hasVersion(module, version)) {
527 throw new CVSException(VersionControlException.DUPLICATE_VERSION, new Object[]{module.getName(), version.getVersionNumber()});
528 }
529 tag(module, Utils.createSymbolicName(module, version), false);
530 }
531
532
533
534
535 private void tag(Module module, SymbolicName symbolicName, boolean branch) throws CVSException {
536
537 TagCommand tagCommand = new TagCommand();
538 tagCommand.setRecursive(true);
539 tagCommand.setTag(symbolicName.getSymbolicName());
540 tagCommand.setMakeBranchTag(branch);
541
542 executeOnCVS(tagCommand, module.getBaseDir(), null);
543 }
544
545 /***
546 * Provide log information on a module. This method checks if the
547 * {@link #setCommandResponse(nl.toolforge.karma.core.cmd.CommandResponse)} method has been called, which results in
548 * this runner initializing the CVS API with the correct objects. This is a requirement for the Netbeans CVS API.
549 */
550 public LogInformation log(Module module) throws CVSException {
551
552 Map arguments = new Hashtable();
553 arguments.put("MODULE", module.getName());
554 arguments.put("REPOSITORY", module.getLocation().getId());
555
556 RlogCommand logCommand = new RlogCommand();
557 logCommand.setModule(getModuleOffset(module) + "/" + Module.MODULE_DESCRIPTOR);
558 logCommand.setRecursive(false);
559
560 executeOnCVS(logCommand, MyFileUtils.getSystemTempDirectory(), arguments);
561
562
563
564 if (isExt) {
565 return ((LogParser) this.listener).getLogInformation();
566 } else {
567 return ((CVSResponseAdapter) this.listener).getLogInformation();
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 * Checks if the module has a CVS branch tag <code>module.getPatchLine().getName()</code> attached.
601 *
602 * @param module The module for which the patch line should be checked.
603 * @return <code>true</code> of the module has a patch line attached in the CVS repository, <code>false</code>
604 * otherwise.
605 */
606 public boolean hasPatchLine(Module module) {
607 try {
608 return hasSymbolicName(module, new CVSTag(new PatchLine(module.getVersion()).getName()));
609 } catch (CVSException c) {
610 return false;
611 }
612 }
613
614 /***
615 * Creates a patchline for the module, given the modules' current version.
616 *
617 * @param module The module.
618 * @throws CVSException When an error occurred during the creation process.
619 */
620 public void createPatchLine(Module module) throws CVSException {
621 try {
622
623
624 String author = ((CVSRepository) module.getLocation()).getUsername();
625 tag(module, new CVSTag(module.getPatchLine().getName()), true);
626
627 addModuleHistoryEvent(module, ModuleHistoryEvent.CREATE_PATCH_LINE_EVENT, module.getVersion(), new Date(), author, "Patch line created by Karma");
628
629 } catch (ModuleHistoryException mhe) {
630 logger.error("Writing the history.xml failed", mhe);
631 throw new CVSException(CVSException.MODULE_HISTORY_ERROR, new Object[]{mhe.getMessage()});
632 }
633 }
634
635 /***
636 * A check if a module exists is done by trying to checkout the modules' <code>module.info</code> file in a temporary
637 * location. If that succeeds, apparently the module exists in that location and we have a <code>true</code> to
638 * return.
639 */
640 public boolean existsInRepository(Module module) {
641
642 if (module == null) {
643 return false;
644 }
645
646 try {
647 RlogCommand logCommand = new RlogCommand();
648 logCommand.setModule(getModuleOffset(module));
649
650 executeOnCVS(logCommand, MyFileUtils.getSystemTempDirectory(), null);
651 } catch (CVSException e) {
652 return false;
653 }
654 return true;
655 }
656
657 private boolean hasVersion(Module module, Version version) throws CVSException {
658 return hasSymbolicName(module, Utils.createSymbolicName(module, version));
659 }
660
661 private boolean hasSymbolicName(Module module, SymbolicName symbolicName) throws CVSException {
662
663 if (symbolicName == null) {
664 return false;
665 }
666
667 LogInformation logInfo = log(module);
668 List symbolicNames = new ArrayList();
669
670 for (Iterator i = logInfo.getAllSymbolicNames().iterator(); i.hasNext();) {
671 symbolicNames.add(((LogInformation.SymName) i.next()).getName());
672 }
673
674 return symbolicNames.contains(symbolicName.getSymbolicName());
675 }
676
677 /***
678 * Runs a CVS command on the repository (through the Netbeans API). When a co
679 *
680 * @param command A command object, representing the CVS command.
681 * @param contextDirectory The directory from where the command should be run.
682 * @param args Arguments that can be passed to the CVSRuntimeException thrown by the CVSListener.
683 *
684 * @throws CVSException When CVS has reported an error through its listener.
685 */
686 private void executeOnCVS(org.netbeans.lib.cvsclient.command.Command command,
687 File contextDirectory, Map args) throws CVSException {
688
689
690 if (isExt) {
691
692 ExtClient client = new ExtClient();
693 client.addCVSListener((LogParser) listener);
694 client.runCommand(command, contextDirectory, getLocation());
695
696 } else {
697
698 if (contextDirectory == null) {
699 throw new NullPointerException("Context directory cannot be null.");
700 }
701
702 CVSException exception = null;
703
704 int retries = 0;
705
706 while (retries < 3) {
707
708 Client client = new Client(getConnection(), new StandardAdminHandler());
709 client.setLocalPath(contextDirectory.getPath());
710
711 if (retries == 0) {
712 logger.debug("Running CVS command : '" + command.getCVSCommand() + "' in " + client.getLocalPath());
713 }
714
715 try {
716
717
718
719
720 long start = System.currentTimeMillis();
721
722 ((CVSResponseAdapter) listener).setArguments(args);
723 client.getEventManager().addCVSListener(listener);
724 client.executeCommand(command, globalOptions);
725
726 logger.debug("CVS command finished in " + (System.currentTimeMillis() - start) + " ms.");
727
728
729
730 break;
731
732 } catch (CommandException e) {
733 logger.error(e.getMessage(), e);
734 if (e.getUnderlyingException() instanceof CVSRuntimeException) {
735 ErrorCode code = ((CVSRuntimeException) e.getUnderlyingException()).getErrorCode();
736 Object[] messageArgs = ((CVSRuntimeException) e.getUnderlyingException()).getMessageArguments();
737 throw new CVSException(code, messageArgs);
738 } else {
739 throw new CVSException(CVSException.INTERNAL_ERROR, new Object[]{globalOptions.getCVSRoot()});
740 }
741 } catch (AuthenticationException e) {
742
743
744 if (retries == 3) {
745 if (e.getUnderlyingThrowable() instanceof IOException) {
746 logger.error("Cannot retry any longer.", e);
747 }
748 exception = new CVSException(CVSException.INTERNAL_ERROR, new Object[]{client.getGlobalOptions().getCVSRoot()});
749 } else {
750 logger.info("Retrying CVS command : '" + command.getCVSCommand() + "' in " + client.getLocalPath());
751
752 }
753 retries ++;
754 } finally {
755
756
757 try {
758 client.getConnection().close();
759 } catch (IOException e) {
760 throw new CVSException(CVSException.INTERNAL_ERROR, new Object[]{globalOptions.getCVSRoot()});
761 }
762 }
763 }
764
765 if (exception != null) {
766 throw exception;
767 }
768 }
769 }
770
771 /***
772 * Helper method that add a new event to a module's history. When the history does not
773 * exist yet (in case of a new module) it is newly created. When the history does exist
774 * the event is added to the history.
775 *
776 * @param module The module involved.
777 * @param eventType The type of {@link ModuleHistoryEvent}.
778 * @param version The version that the module is promoted to.
779 * @param datetime The timestamp.
780 * @param author The authentication of the person who has triggered the event.
781 * @param comment The (optional) comment the author has given.
782 * @throws CVSException Thrown in case something goes wrong with CVS
783 */
784 private void addModuleHistoryEvent(
785 Module module,
786 String eventType,
787 Version version,
788 Date datetime,
789 String author,
790 String comment) throws CVSException, ModuleHistoryException
791 {
792 ModuleHistoryFactory factory = ModuleHistoryFactory.getInstance(module.getBaseDir());
793 ModuleHistory history = factory.getModuleHistory(module);
794 if (history != null) {
795 ModuleHistoryEvent event = new ModuleHistoryEvent();
796 event.setType(eventType);
797 event.setVersion(version);
798 event.setDatetime(datetime);
799 event.setAuthor(author);
800 event.setComment(comment);
801 history.addEvent(event);
802
803 try {
804 MyFileUtils.makeWriteable(new File(module.getBaseDir(), "CVS"), false);
805 if (history.getHistoryLocation().exists()) {
806
807 MyFileUtils.makeWriteable(history.getHistoryLocation(), false);
808 history.save();
809
810
811 commit(null, module, new File(module.getBaseDir(), ModuleHistory.MODULE_HISTORY_FILE_NAME), "History updated by Karma");
812 } else {
813
814 history.save();
815 add(module, new String[]{history.getHistoryLocation().getName()}, null);
816 }
817 MyFileUtils.makeReadOnly(module.getBaseDir());
818 } catch (IOException ioe) {
819 logger.error("Error when making history.xml readonly/writeable", ioe);
820 } catch (InterruptedException ie) {
821 logger.error("Error when making history.xml readonly/writeable", ie);
822 }
823 } else {
824
825 throw new OutOfTheBlueException("If this happens something rrreally went wrong with the history");
826 }
827 }
828
829 }
830