Java tutorial
/* Karma core - Core of the Karma application Copyright (C) 2004 Toolforge <www.toolforge.nl> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package nl.toolforge.karma.core.vc.cvsimpl; import net.sf.sillyexceptions.OutOfTheBlueException; import nl.toolforge.core.util.file.MyFileUtils; import nl.toolforge.karma.core.ErrorCode; import nl.toolforge.karma.core.KarmaRuntimeException; import nl.toolforge.karma.core.Version; import nl.toolforge.karma.core.cmd.Command; import nl.toolforge.karma.core.cmd.CommandResponse; import nl.toolforge.karma.core.history.ModuleHistory; import nl.toolforge.karma.core.history.ModuleHistoryEvent; import nl.toolforge.karma.core.history.ModuleHistoryException; import nl.toolforge.karma.core.history.ModuleHistoryFactory; import nl.toolforge.karma.core.location.Location; import nl.toolforge.karma.core.module.Module; import nl.toolforge.karma.core.vc.Authenticator; import nl.toolforge.karma.core.vc.Authenticators; import nl.toolforge.karma.core.vc.DevelopmentLine; import nl.toolforge.karma.core.vc.PatchLine; import nl.toolforge.karma.core.vc.Runner; import nl.toolforge.karma.core.vc.SymbolicName; import nl.toolforge.karma.core.vc.VersionControlException; import nl.toolforge.karma.core.vc.VersionControlSystem; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.netbeans.lib.cvsclient.Client; import org.netbeans.lib.cvsclient.admin.StandardAdminHandler; import org.netbeans.lib.cvsclient.command.CommandException; import org.netbeans.lib.cvsclient.command.GlobalOptions; import org.netbeans.lib.cvsclient.command.add.AddCommand; import org.netbeans.lib.cvsclient.command.checkout.CheckoutCommand; import org.netbeans.lib.cvsclient.command.commit.CommitCommand; import org.netbeans.lib.cvsclient.command.importcmd.ImportCommand; import org.netbeans.lib.cvsclient.command.log.LogInformation; import org.netbeans.lib.cvsclient.command.log.RlogCommand; import org.netbeans.lib.cvsclient.command.tag.TagCommand; import org.netbeans.lib.cvsclient.command.update.UpdateCommand; import org.netbeans.lib.cvsclient.connection.AuthenticationException; import org.netbeans.lib.cvsclient.connection.Connection; import org.netbeans.lib.cvsclient.connection.ConnectionFactory; import org.netbeans.lib.cvsclient.connection.PServerConnection; import org.netbeans.lib.cvsclient.event.CVSListener; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; /** * <p>Runner class for CVS. Executes stuff on a CVS repository. * <p/> * <p>TODO : the CVSRunner could be made multi-threaded, to use bandwidth to a remote repository much better ... * * @author D.A. Smedes * @version $Id$ */ public final class CVSRunner implements Runner { static { // As per the recommendation ... // System.setProperty("javacvs.multiple_commands_warning", "false"); } private CVSListener listener = null; // The listener that receives events from this runner. private static Log logger = LogFactory.getLog(CVSRunner.class); private GlobalOptions globalOptions = new GlobalOptions(); private Connection connection = null; private CVSRepository location = null; private boolean isExt = false; /** * Constructs a runner to fire commands on a CVS repository. A typical client for a <code>CVSRunner</code> instance is * a {@link Command} implementation, as that one knows what to fire away on CVS. The runner is instantiated with a * <code>location</code> and a <code>manifest</code>. The location must be a <code>CVSLocationImpl</code> instance, * reprenting a CVS repository. The manifest is required because it determines the base point from where CVS commands * will be run; modules are checked out in a directory structure, relative to * {@link nl.toolforge.karma.core.manifest.Manifest#getModuleBaseDirectory()}. * * @param location A <code>Location</code> instance (typically a <code>CVSLocationImpl</code> instance), containing * the location and connection details of the CVS repository. * @throws CVSException <code>AUTHENTICATION_ERROR</code> is thrown when <code>location</code> cannot be authenticated. * @throws nl.toolforge.karma.core.vc.AuthenticationException If the location cannot be authenticated. */ public CVSRunner(Location location) throws CVSException, nl.toolforge.karma.core.vc.AuthenticationException { if (location == null) { throw new CVSException(VersionControlException.MISSING_LOCATION); } CVSRepository cvsLocation = null; try { cvsLocation = ((CVSRepository) location); setLocation(cvsLocation); } catch (ClassCastException e) { logger.error("Wrong type for location. Should be CVSRepository.", e); throw new KarmaRuntimeException("Wrong type for location. Should be CVSRepository.", e); } //If we don't have a // Authenticator a = Authenticators.getAuthenticator(cvsLocation.getAuthenticatorKey()); cvsLocation.setUsername(a.getUsername()); if (cvsLocation.getProtocol().equals(CVSRepository.EXT)) { isExt = true; this.listener = new LogParser(); } else { this.listener = new CVSResponseAdapter(); connection = ConnectionFactory.getConnection(cvsLocation.getCVSRoot()); if (connection instanceof PServerConnection) { ((PServerConnection) connection).setEncodedPassword(a.getPassword()); } logger.debug("CVSRunner using CVSROOT : " + cvsLocation.getCVSRoot()); globalOptions.setCVSRoot(cvsLocation.getCVSRoot()); } // The default ... // } private void setLocation(CVSRepository location) { this.location = location; } private CVSRepository getLocation() { return location; } private Connection getConnection() { return connection; } /** * Assigns a CommandResponse instance to the runner to optionally promote interactivity. * * @param response A - possibly <code>null</code> response instance. */ public void setCommandResponse(CommandResponse response) { listener = new CVSResponseAdapter(response); } /** * Checks if the module is located in CVS within a subdirectory (or subdirs, any amount is possible). If so, the * module-name is prefixed with that offset. * * @param module * @return The module-name, or - when applicable - the module-name prefixed with the value of * {@link VersionControlSystem#getModuleOffset()}. */ private String getModuleOffset(Module module) { // todo when modules from different locations have the same name, they should be given a namespace in front // of the module. if (((VersionControlSystem) module.getLocation()).getModuleOffset() == null) { return module.getName(); } else { return ((VersionControlSystem) module.getLocation()).getModuleOffset() + "/" + module.getName(); } } public void commit(File file) throws VersionControlException { // todo why not use the add()-method ???? StandardAdminHandler adminHandler = new StandardAdminHandler(); boolean isEntry = false; try { isEntry = (adminHandler.getEntry(file) != null); } catch (IOException e) { isEntry = false; } if (isEntry) { CommitCommand commitCommand = new CommitCommand(); commitCommand.setFiles(new File[] { file }); commitCommand.setMessage("<undocumented> File committed by Karma"); executeOnCVS(commitCommand, file.getParentFile(), null); } else { AddCommand addCommand = new AddCommand(); addCommand.setFiles(new File[] { file }); executeOnCVS(addCommand, file.getParentFile(), null); CommitCommand commitCommand = new CommitCommand(); commitCommand.setFiles(new File[] { file }); commitCommand.setMessage("<undocumented> File committed by Karma"); executeOnCVS(commitCommand, file.getParentFile(), null); } } public void addModule(Module module, String comment) throws CVSException { // Step 1 : check if the module doesn't yet exist // if (existsInRepository(module)) { throw new CVSException(CVSException.MODULE_EXISTS_IN_REPOSITORY, new Object[] { module.getName(), module.getLocation().getId() }); } // Step 2 : import the module, including its full structure. // ImportCommand importCommand = new ImportCommand(); importCommand.setModule(getModuleOffset(module)); importCommand.setLogMessage( "Module " + module.getName() + " created automatically by Karma on " + new Date().toString()); importCommand.setVendorTag("Karma"); importCommand.setReleaseTag("MAINLINE_0-0"); executeOnCVS(importCommand, module.getBaseDir(), null); } /** * Performs the <code>cvs checkout [-r <symbolic-name>] <module></code>command for a module. * <code>version</code> is used when not <code>null</code> to checkout a module with a symbolic name. * * @param module The module to check out. * @param version The version number for the module to check out. * @throws CVSException With errorcodes <code>NO_SUCH_MODULE_IN_REPOSITORY</code> when the module does not exist * in the repository and <code>INVALID_SYMBOLIC_NAME</code>, when the version does not exists for the module. */ public void checkout(Module module, Version version) throws CVSException { checkout(module, null, version); } public void checkout(Module module) throws CVSException { checkout(module, null, null); } /** * See {@link #checkout(Module, Version)}. This method defaults to the HEAD of the development branch at hand. * * @param module The module to check out. * @param developmentLine The development line or <code>null</code> when the TRUNK is the context line. * @throws CVSException With errorcodes <code>NO_SUCH_MODULE_IN_REPOSITORY</code> when the module does not exist * in the repository and <code>INVALID_SYMBOLIC_NAME</code>, when the version does not exists * for the module. */ public void checkout(Module module, DevelopmentLine developmentLine, Version version) throws CVSException { //todo: proper exception handling try { MyFileUtils.makeWriteable(module.getBaseDir(), true); } catch (Exception e) { logger.error("Exception when making module writeable just before checking it out.", e); } boolean readonly = false; Map arguments = new Hashtable(); arguments.put("MODULE", module.getName()); arguments.put("REPOSITORY", module.getLocation().getId()); CheckoutCommand checkoutCommand = new CheckoutCommand(); checkoutCommand.setModule(getModuleOffset(module)); checkoutCommand.setCheckoutDirectory(module.getName()); // Flatten to module name as the checkoutdir. checkoutCommand.setPruneDirectories(true); //-P if ((version != null) || (developmentLine != null)) { checkoutCommand.setCheckoutByRevision( Utils.createSymbolicName(module, developmentLine, version).getSymbolicName()); if (version != null) { arguments.put("VERSION", version.toString()); readonly = true; } } else { checkoutCommand.setResetStickyOnes(true); } // The checkout directory for a module has to be relative to executeOnCVS(checkoutCommand, module.getBaseDir().getParentFile(), arguments); updateModuleHistoryXml(module); if (readonly) { MyFileUtils.makeReadOnly(module.getBaseDir()); } } private void updateModuleHistoryXml(Module module) throws CVSException { logger.debug("Updating history.xml to HEAD."); UpdateCommand command = new UpdateCommand(); try { ModuleHistoryFactory factory = ModuleHistoryFactory.getInstance(module.getBaseDir()); File historyLocation = factory.getModuleHistory(module).getHistoryLocation(); command.setFiles(new File[] { historyLocation }); command.setResetStickyOnes(true); //-A executeOnCVS(command, module.getBaseDir(), null); logger.debug("Done updating history.xml to HEAD."); } catch (ModuleHistoryException mhe) { logger.error("ModuleHistoryException when updating module history to HEAD", mhe); } } /** * @see #update(Module, DevelopmentLine, Version) */ public void update(Module module) throws CVSException { update(module, null); } /** * @see #update(Module, DevelopmentLine, Version) */ public void update(Module module, Version version) throws CVSException { update(module, null, version); } /** * For a module, the <code>cvs -q update -d -r <symbolic-name></code> command is executed. Note that empty * directories are not pruned. * * @param module The module to update. * @param developmentLine The development line or <code>null</code> when the TRUNK is the context line. * @param version The version of the module or <code>null</code> when no specific version applies. */ public void update(Module module, DevelopmentLine developmentLine, Version version) throws CVSException { //todo: proper exception handling try { MyFileUtils.makeWriteable(module.getBaseDir(), true); } catch (Exception e) { logger.error("Exception when making module writeable just before updating it.", e); } boolean readonly = false; Map arguments = new Hashtable(); arguments.put("MODULE", module.getName()); arguments.put("REPOSITORY", module.getLocation().getId()); UpdateCommand updateCommand = new UpdateCommand(); updateCommand.setRecursive(true); updateCommand.setBuildDirectories(true); //-d updateCommand.setPruneDirectories(false); //-P if (version != null || module.hasPatchLine()) { updateCommand.setUpdateByRevision( Utils.createSymbolicName(module, developmentLine, version).getSymbolicName()); if (version != null) { arguments.put("VERSION", version.toString()); readonly = true; } } else { updateCommand.setResetStickyOnes(true); } // todo add data to the exception. this sort of business logic should be here, not in CVSResponseAdapter. // executeOnCVS(updateCommand, module.getBaseDir(), arguments); updateModuleHistoryXml(module); if (readonly) { MyFileUtils.makeReadOnly(module.getBaseDir()); } } public void add(Module module, File[] files, File[] dirs) throws CVSException { String[] f = (files == null ? new String[0] : new String[files.length]); String[] d = (dirs == null ? new String[0] : new String[dirs.length]); for (int i = 0; i < f.length; i++) { f[i] = files[i].getPath(); } for (int i = 0; i < d.length; i++) { d[i] = dirs[i].getPath(); } add(module, f, d); } public void add(Module module, String[] files, String[] dirs) throws CVSException { files = (files == null ? new String[] {} : files); dirs = (dirs == null ? new String[] {} : dirs); Map arguments = new Hashtable(); arguments.put("MODULE", module.getName()); // Step 1 : Add the file to the CVS repository // AddCommand addCommand = new AddCommand(); addCommand.setMessage("Initial checkin in repository by Karma."); File modulePath = module.getBaseDir(); // Create temp files // Collection cvsFilesCollection = new ArrayList(); int i = 0; for (i = 0; i < files.length; i++) { File fileToAdd = new File(modulePath, files[i]); if (!fileToAdd.exists()) { try { File dir = new File(modulePath, files[i]).getParentFile(); if (dir.mkdirs()) { cvsFilesCollection.add(dir); } fileToAdd.createNewFile(); logger.debug("Created file " + files[i] + " for module " + module.getName() + "."); } catch (IOException e) { throw new KarmaRuntimeException( "Error while creating module layout for module " + module.getName()); } } cvsFilesCollection.add(fileToAdd); } // Create temp directories // for (i = 0; i < dirs.length; i++) { File dirToAdd = new File(modulePath, dirs[i]); //try to create the dirs if (!dirToAdd.mkdirs() && !dirToAdd.exists()) { //failed to create the dirs and they do not exist yet. throw new KarmaRuntimeException( "Error while creating module layout for module " + module.getName()); } logger.debug("Created directory " + dirs[i] + " for module " + module.getName() + "."); // Ensure that all directories are added correctly // StringTokenizer tokenizer = new StringTokenizer(dirs[i], "/"); String base = ""; while (tokenizer.hasMoreTokens()) { String subDir = tokenizer.nextToken(); base += subDir; cvsFilesCollection.add(new File(modulePath, base)); base += "/"; } } File[] cvsFiles = (File[]) cvsFilesCollection.toArray(new File[cvsFilesCollection.size()]); addCommand.setFiles(cvsFiles); // A file is added against a module, thus the contextDirectory is constructed based on the basePoint and the // modules' name. // executeOnCVS(addCommand, module.getBaseDir(), arguments); // Step 2 : Commit the file to the CVS repository // CommitCommand commitCommand = new CommitCommand(); commitCommand.setFiles(cvsFiles); commitCommand.setMessage("File added automatically by Karma."); executeOnCVS(commitCommand, module.getBaseDir(), arguments); } private void commit(DevelopmentLine developmentLine, Module module, File file, String message) throws CVSException { CommitCommand commitCommand = new CommitCommand(); commitCommand.setFiles(new File[] { file }); if (developmentLine != null) { commitCommand.setToRevisionOrBranch(developmentLine.getName()); } commitCommand.setMessage(message); executeOnCVS(commitCommand, module.getBaseDir(), null); } public void promote(Module module, String comment, Version version) throws CVSException { try { //Add an event to the module history. String author = ((CVSRepository) module.getLocation()).getUsername(); addModuleHistoryEvent(module, ModuleHistoryEvent.PROMOTE_MODULE_EVENT, version, new Date(), author, comment); tag(module, version); } catch (ModuleHistoryException mhe) { logger.error("Writing the history.xml failed", mhe); throw new CVSException(CVSException.MODULE_HISTORY_ERROR, new Object[] { mhe.getMessage() }); } } private void tag(Module module, Version version) throws CVSException { if (hasVersion(module, version)) { throw new CVSException(VersionControlException.DUPLICATE_VERSION, new Object[] { module.getName(), version.getVersionNumber() }); } tag(module, Utils.createSymbolicName(module, version), false); } // // Private for the time being // private void tag(Module module, SymbolicName symbolicName, boolean branch) throws CVSException { TagCommand tagCommand = new TagCommand(); tagCommand.setRecursive(true); tagCommand.setTag(symbolicName.getSymbolicName()); tagCommand.setMakeBranchTag(branch); executeOnCVS(tagCommand, module.getBaseDir(), null); } /** * Provide log information on a module. This method checks if the * {@link #setCommandResponse(nl.toolforge.karma.core.cmd.CommandResponse)} method has been called, which results in * this runner initializing the CVS API with the correct objects. This is a requirement for the Netbeans CVS API. */ public LogInformation log(Module module) throws CVSException { Map arguments = new Hashtable(); arguments.put("MODULE", module.getName()); arguments.put("REPOSITORY", module.getLocation().getId()); RlogCommand logCommand = new RlogCommand(); logCommand.setModule(getModuleOffset(module) + "/" + Module.MODULE_DESCRIPTOR); logCommand.setRecursive(false); executeOnCVS(logCommand, MyFileUtils.getSystemTempDirectory(), arguments); // Another hook into ext support. // if (isExt) { return ((LogParser) this.listener).getLogInformation(); } else { return ((CVSResponseAdapter) this.listener).getLogInformation(); } } /* DISABLED, DO NOT DELETE, EXPERIMENTAL public LogInformation log(Manifest manifest) throws CVSException { RlogCommand logCommand = new RlogCommand(); String[] modules = new String[manifest.getAllModules().size()]; int j = 0; for (Iterator i = manifest.getAllModules().values().iterator(); i.hasNext();) { modules[j] = getModuleOffset((Module) i.next()) + "/" + Module.MODULE_DESCRIPTOR; j++; } logCommand.setModules(modules); executeOnCVS(logCommand, new File("."), null); // Another hook into ext support. // return ((CVSResponseAdapter) this.listener).getLogInformation(); } */ /** * Checks if the module has a CVS branch tag <code>module.getPatchLine().getName()</code> attached. * * @param module The module for which the patch line should be checked. * @return <code>true</code> of the module has a patch line attached in the CVS repository, <code>false</code> * otherwise. */ public boolean hasPatchLine(Module module) { try { return hasSymbolicName(module, new CVSTag(new PatchLine(module.getVersion()).getName())); } catch (CVSException c) { return false; } } /** * Creates a patchline for the module, given the modules' current version. * * @param module The module. * @throws CVSException When an error occurred during the creation process. */ public void createPatchLine(Module module) throws CVSException { try { // Add an event to the module history. // String author = ((CVSRepository) module.getLocation()).getUsername(); tag(module, new CVSTag(module.getPatchLine().getName()), true); addModuleHistoryEvent(module, ModuleHistoryEvent.CREATE_PATCH_LINE_EVENT, module.getVersion(), new Date(), author, "Patch line created by Karma"); } catch (ModuleHistoryException mhe) { logger.error("Writing the history.xml failed", mhe); throw new CVSException(CVSException.MODULE_HISTORY_ERROR, new Object[] { mhe.getMessage() }); } } /** * A check if a module exists is done by trying to checkout the modules' <code>module.info</code> file in a temporary * location. If that succeeds, apparently the module exists in that location and we have a <code>true</code> to * return. */ public boolean existsInRepository(Module module) { if (module == null) { return false; } try { RlogCommand logCommand = new RlogCommand(); logCommand.setModule(getModuleOffset(module)); executeOnCVS(logCommand, MyFileUtils.getSystemTempDirectory(), null); } catch (CVSException e) { return false; } return true; } private boolean hasVersion(Module module, Version version) throws CVSException { return hasSymbolicName(module, Utils.createSymbolicName(module, version)); } private boolean hasSymbolicName(Module module, SymbolicName symbolicName) throws CVSException { if (symbolicName == null) { return false; } LogInformation logInfo = log(module); List symbolicNames = new ArrayList(); for (Iterator i = logInfo.getAllSymbolicNames().iterator(); i.hasNext();) { symbolicNames.add(((LogInformation.SymName) i.next()).getName()); } return symbolicNames.contains(symbolicName.getSymbolicName()); } /** * Runs a CVS command on the repository (through the Netbeans API). When a co * * @param command A command object, representing the CVS command. * @param contextDirectory The directory from where the command should be run. * @param args Arguments that can be passed to the CVSRuntimeException thrown by the CVSListener. * * @throws CVSException When CVS has reported an error through its listener. */ private void executeOnCVS(org.netbeans.lib.cvsclient.command.Command command, File contextDirectory, Map args) throws CVSException { // Switch ... // if (isExt) { ExtClient client = new ExtClient(); client.addCVSListener((LogParser) listener); client.runCommand(command, contextDirectory, getLocation()); } else { if (contextDirectory == null) { throw new NullPointerException("Context directory cannot be null."); } CVSException exception = null; int retries = 0; while (retries < 3) { Client client = new Client(getConnection(), new StandardAdminHandler()); client.setLocalPath(contextDirectory.getPath()); if (retries == 0) { logger.debug( "Running CVS command : '" + command.getCVSCommand() + "' in " + client.getLocalPath()); } try { // A CVSResponseAdapter is registered as a listener for the response from CVS. This one adapts to Karma // specific stuff. // long start = System.currentTimeMillis(); ((CVSResponseAdapter) listener).setArguments(args); client.getEventManager().addCVSListener(listener); client.executeCommand(command, globalOptions); logger.debug("CVS command finished in " + (System.currentTimeMillis() - start) + " ms."); // Apparently, were done succesfully. // break; } catch (CommandException e) { logger.error(e.getMessage(), e); if (e.getUnderlyingException() instanceof CVSRuntimeException) { ErrorCode code = ((CVSRuntimeException) e.getUnderlyingException()).getErrorCode(); Object[] messageArgs = ((CVSRuntimeException) e.getUnderlyingException()) .getMessageArguments(); throw new CVSException(code, messageArgs); } else { throw new CVSException(CVSException.INTERNAL_ERROR, new Object[] { globalOptions.getCVSRoot() }); } } catch (AuthenticationException e) { // Over here, we have implemented a retry-mechanism. // if (retries == 3) { if (e.getUnderlyingThrowable() instanceof IOException) { logger.error("Cannot retry any longer.", e); } exception = new CVSException(CVSException.INTERNAL_ERROR, new Object[] { client.getGlobalOptions().getCVSRoot() }); } else { logger.info("Retrying CVS command : '" + command.getCVSCommand() + "' in " + client.getLocalPath()); // logger.error(e.getMessage(), e); } retries++; } finally { // See the static block in this class and corresponding documentation. // try { client.getConnection().close(); } catch (IOException e) { throw new CVSException(CVSException.INTERNAL_ERROR, new Object[] { globalOptions.getCVSRoot() }); } } } if (exception != null) { throw exception; } } } /** * Helper method that add a new event to a module's history. When the history does not * exist yet (in case of a new module) it is newly created. When the history does exist * the event is added to the history. * * @param module The module involved. * @param eventType The type of {@link ModuleHistoryEvent}. * @param version The version that the module is promoted to. * @param datetime The timestamp. * @param author The authentication of the person who has triggered the event. * @param comment The (optional) comment the author has given. * @throws CVSException Thrown in case something goes wrong with CVS */ private void addModuleHistoryEvent(Module module, String eventType, Version version, Date datetime, String author, String comment) throws CVSException, ModuleHistoryException { ModuleHistoryFactory factory = ModuleHistoryFactory.getInstance(module.getBaseDir()); ModuleHistory history = factory.getModuleHistory(module); if (history != null) { ModuleHistoryEvent event = new ModuleHistoryEvent(); event.setType(eventType); event.setVersion(version); event.setDatetime(datetime); event.setAuthor(author); event.setComment(comment); history.addEvent(event); File cvsEntriesFile = new File(module.getBaseDir(), "CVS" + File.separator + "Entries"); File historyFile = history.getHistoryLocation(); boolean isCvsEntriesFileReadOnly = !cvsEntriesFile.canWrite(); boolean isHistoryFileReadOnly = !historyFile.canWrite(); try { if (isCvsEntriesFileReadOnly) { MyFileUtils.makeWriteable(cvsEntriesFile, false); } if (historyFile.exists()) { //history already exists. commit changes. if (isHistoryFileReadOnly) { MyFileUtils.makeWriteable(historyFile, false); } history.save(); //development line is null, since the history.xml is always committed in the HEAD. commit(null, module, new File(module.getBaseDir(), ModuleHistory.MODULE_HISTORY_FILE_NAME), "History updated by Karma"); } else { //history did not exist yet. add to CVS and commit it. history.save(); add(module, new String[] { historyFile.getName() }, null); } } catch (IOException ioe) { logger.error("Error when making history.xml readonly/writeable", ioe); } catch (InterruptedException ie) { logger.error("Error when making history.xml readonly/writeable", ie); } finally { if (isCvsEntriesFileReadOnly) { cvsEntriesFile.setReadOnly(); } if (isHistoryFileReadOnly) { historyFile.setReadOnly(); } } } else { //history wel null. EN NU?! todo throw new OutOfTheBlueException("If this happens something rrreally went wrong with the history"); } } }