/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.netbeans.lib.cvsclient.command.commit; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.StringTokenizer; import org.netbeans.lib.cvsclient.ClientServices; import org.netbeans.lib.cvsclient.admin.Entry; import org.netbeans.lib.cvsclient.command.BasicCommand; import org.netbeans.lib.cvsclient.command.Builder; import org.netbeans.lib.cvsclient.command.CommandException; import org.netbeans.lib.cvsclient.connection.AuthenticationException; import org.netbeans.lib.cvsclient.event.EventManager; import org.netbeans.lib.cvsclient.request.ArgumentRequest; import org.netbeans.lib.cvsclient.request.ArgumentxRequest; import org.netbeans.lib.cvsclient.request.CommandRequest; import org.netbeans.lib.cvsclient.request.DirectoryRequest; import org.netbeans.lib.cvsclient.request.StickyRequest; /** * The command to commit any changes that have been made. * * @author Robert Greig */ public class CommitCommand extends BasicCommand { /** * The argument requests that must be added at the end. These argument requests indicate the files to be committed */ private final List argumentRequests = new LinkedList(); /** * The log message used for the commit. */ private String message; /** * Forces the commit of the file(s) even if no changes were done. the standard behaviour is NOT-TO-BE recursive in this case. */ private boolean forceCommit; /** * The filename for the file that defines the message. */ private String logMessageFromFile; /** * Determines that no module program should run on the server. */ private boolean noModuleProgram; /** Holds value of property toRevisionOrBranch. */ private String toRevisionOrBranch; /** * Construct a CommitCommand. */ public CommitCommand() { resetCVSCommand(); } /** * Returns the commit message. */ public String getMessage() { return message; } /** * Sets the commit message. */ public void setMessage(String message) { this.message = message; } /** * Indicates whether the commit should be forced even if there are no changes. */ public boolean isForceCommit() { return forceCommit; } /** * Sets whether the commit should be forced even if there are no changes. */ public void setForceCommit(boolean forceCommit) { this.forceCommit = forceCommit; } /** * Adds the appropriate requests for a given directory. Sends a directory request followed by as many Entry and Modified requests as * required. * * @param directory * the directory to send requests for * @throws IOException * if an error occurs constructing the requests */ @Override protected void addRequestsForDirectory(File directory) throws IOException { if (!directory.exists()) { return; } // remove localPath prefix from directory. If left with // nothing, use dot (".") in the directory request. Also remove the // trailing slash String dir = getRelativeToLocalPathInUnixStyle(directory); try { String repository = clientServices.getRepositoryForDirectory(directory.getAbsolutePath()); requests.add(new DirectoryRequest(dir, repository)); String tag = clientServices.getStickyTagForDirectory(directory); if (tag != null) { requests.add(new StickyRequest(tag)); } } catch (IOException ex) { System.err.println("An error occurred reading the respository " + "for the directory " + dir + ": " + ex); ex.printStackTrace(); } // Obtain a set of all files known to CVS. We union // this set with the set of files in the actual filesystem directory // to obtain a set of files to commit (or at least attempt to commit). Set set = clientServices.getAllFiles(directory); // We must add the local files (and directories) because the above // command does *not* return cvs controlled directories final File[] files = directory.listFiles(); // get the union of the files in the directory and the files retrieved // from the Entries file. set.addAll(Arrays.asList(files)); List subdirectories = null; if (isRecursive()) { subdirectories = new LinkedList(); } for (Iterator it = set.iterator(); it.hasNext();) { File file = (File) it.next(); if (file.getName().equals("CVS")) { // NOI18N continue; } try { final Entry entry = clientServices.getEntry(file); // a non-null entry means the file does exist in the // Entries file for this directory if (entry == null) { continue; } // here file.isFile() is *not* used, because not existing // files (removed ones) should also be sent if (file.isFile()) { sendEntryAndModifiedRequests(entry, file); } else if (isRecursive() && file.isDirectory()) { File cvsSubDir = new File(file, "CVS"); // NOI18N if (cvsSubDir.exists()) { subdirectories.add(file); } } } catch (IOException ex) { System.err.println("An error occurred getting the " + "Entry for file " + file + ": " + ex); ex.printStackTrace(); } } if (isRecursive()) { for (Iterator it = subdirectories.iterator(); it.hasNext();) { File subdirectory = (File) it.next(); addRequestsForDirectory(subdirectory); } } } /** * Add the appropriate requests for a single file. A directory request is sent, followed by an Entry and Modified request. * * @param file * the file to send requests for * @throws IOException * if an error occurs constructing the requests */ @Override protected void addRequestsForFile(File file) throws IOException { final File parentDirectory = file.getParentFile(); // remove localPath prefix from directory. If left with // nothing, use dot (".") in the directory request String dir = getRelativeToLocalPathInUnixStyle(parentDirectory); try { // send a argument request indicating the file to update requests.add(new DirectoryRequest(dir, clientServices.getRepositoryForDirectory(parentDirectory.getAbsolutePath()))); String tag = clientServices.getStickyTagForDirectory(parentDirectory); if (tag != null) { requests.add(new StickyRequest(tag)); } } catch (IOException ex) { System.err.println("An error occurred reading the respository " + "for the directory " + dir + ": " + ex); ex.printStackTrace(); } try { final Entry entry = clientServices.getEntry(file); // a non-null entry means the file does exist in the // Entries file for this directory if (entry != null) { sendEntryAndModifiedRequests(entry, file); } } catch (IOException ex) { System.err.println("An error occurred getting the Entry " + "for file " + file + ": " + ex); ex.printStackTrace(); } } /** * Should return true if unchanged files should not be sent to server. If false is returned, all files will be sent to server This * method is used by <code>sendEntryAndModifiedRequests</code>. */ @Override protected boolean doesCheckFileTime() { return !isForceCommit(); } /** * Execute the command. * * @param client * the client services object that provides any necessary services to this command, including the ability to actually process * all the requests */ @Override public void execute(ClientServices client, EventManager em) throws CommandException, AuthenticationException { client.ensureConnection(); super.execute(client, em); try { // add arguments. if (isForceCommit()) { requests.add(1, new ArgumentRequest("-f")); // NOI18N if (isRecursive()) { requests.add(1, new ArgumentRequest("-R")); // NOI18N } } if (isNoModuleProgram()) { requests.add(1, new ArgumentRequest("-n")); // NOI18N } if (getToRevisionOrBranch() != null) { requests.add(1, new ArgumentRequest("-r")); // NOI18N requests.add(2, new ArgumentRequest(getToRevisionOrBranch())); } // build the message to send String message = getMessage(); if (getLogMessageFromFile() != null) { message = loadLogFile(getLogMessageFromFile()); } if (message != null) { message = message.trim(); } if (message == null || message.length() == 0) { message = "no message"; // NOI18N } addMessageRequest(message); addRequestForWorkingDirectory(client); requests.addAll(argumentRequests); argumentRequests.clear(); // MK sanity check. addArgumentRequests(); requests.add(CommandRequest.COMMIT); client.processRequests(requests); } catch (CommandException ex) { throw ex; } catch (Exception ex) { throw new CommandException(ex, ex.getLocalizedMessage()); } finally { requests.clear(); } } @Override protected void addArgumentRequests() { if (isForceCommit()) { Iterator it = requests.iterator(); String directory = ""; List args = new LinkedList(); while (it.hasNext()) { Object req = it.next(); if (req instanceof org.netbeans.lib.cvsclient.request.DirectoryRequest) { org.netbeans.lib.cvsclient.request.DirectoryRequest dirReq = (org.netbeans.lib.cvsclient.request.DirectoryRequest) req; // haven't checked but I'm almost sure that within the Argument request always the local directory is used. directory = dirReq.getLocalDirectory(); } else if (req instanceof org.netbeans.lib.cvsclient.request.EntryRequest) { org.netbeans.lib.cvsclient.request.EntryRequest entReq = (org.netbeans.lib.cvsclient.request.EntryRequest) req; String argument = null; if (directory.length() == 0) { argument = entReq.getEntry().getName(); } else { argument = directory + '/' + entReq.getEntry().getName(); } args.add(new ArgumentRequest(argument)); } } it = args.iterator(); while (it.hasNext()) { requests.add(it.next()); } } else { super.addArgumentRequests(); } } /** * This method returns how the command would looklike when typed on the command line. Example: checkout -p CvsCommand.java * * @returns <command's name> [<parameters>] files/dirs */ @Override public String getCVSCommand() { StringBuffer toReturn = new StringBuffer("commit "); // NOI18N toReturn.append(getCVSArguments()); File[] files = getFiles(); if (files != null) { for (int index = 0; index < files.length; index++) { toReturn.append(files[index].getName() + " "); // NOI18N } } return toReturn.toString(); } /** * Takes the arguments and sets the command. To be mainly used for automatic settings (like parsing the .cvsrc file). * * @return true if the option (switch) was recognized and set */ @Override public boolean setCVSCommand(char opt, String optArg) { if (opt == 'm') { setMessage(optArg); } else if (opt == 'l') { setRecursive(false); } else if (opt == 'R') { setRecursive(true); } else if (opt == 'f') { setForceCommit(true); } else if (opt == 'F') { setLogMessageFromFile(optArg); } else if (opt == 'r') { setToRevisionOrBranch(optArg); } else if (opt == 'n') { setNoModuleProgram(true); } else { return false; } return true; } /** * Returns a String defining which options are available for this command. */ @Override public String getOptString() { return "m:flRnF:r:"; // NOI18N } /** * Method that is called while the command is being executed. Descendants can override this method to return a Builder instance that * will parse the server's output and create data structures. */ @Override public Builder createBuilder(EventManager eventMan) { return new CommitBuilder(eventMan, getLocalDirectory(), clientServices.getRepository()); } /** * Generates the Argument/Argumentx series of requests depending on the number of lines in the message request. */ private void addMessageRequest(String message) { requests.add(new ArgumentRequest("-m")); // NOI18N StringTokenizer token = new StringTokenizer(message, "\n", false); // NOI18N boolean first = true; while (token.hasMoreTokens()) { if (first) { requests.add(new ArgumentRequest(token.nextToken())); first = false; } else { requests.add(new ArgumentxRequest(token.nextToken())); } } } /** * Returns the filename for the file that defines the message. */ public String getLogMessageFromFile() { return logMessageFromFile; } /** * Sets the filename for the file that defines the message. */ public void setLogMessageFromFile(String logMessageFromFile) { this.logMessageFromFile = logMessageFromFile; } /** * Returns whether no module program should be executed on the server. */ public boolean isNoModuleProgram() { return noModuleProgram; } /** * Sets whether no module program should run on the server */ public void setNoModuleProgram(boolean noModuleProgram) { this.noModuleProgram = noModuleProgram; } /** * Getter for property toRevisionOrBranch. * * @return Value of property toRevisionOrBranch. */ public String getToRevisionOrBranch() { return toRevisionOrBranch; } /** * Setter for property toRevisionOrBranch. * * @param toRevisionOrBranch * New value of property toRevisionOrBranch. */ public void setToRevisionOrBranch(String toRevBranch) { this.toRevisionOrBranch = toRevBranch; } private String loadLogFile(String fileName) throws CommandException { StringBuffer buffer = new StringBuffer(); BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(fileName)); String line; while ((line = reader.readLine()) != null) { buffer.append(line + "\n"); // NOI18N } } catch (FileNotFoundException ex) { throw new CommandException(ex, CommandException.getLocalMessage("CommitCommand.logInfoFileNotExists", new Object[] { fileName })); // NOI18N } catch (IOException ex) { throw new CommandException(ex, CommandException.getLocalMessage("CommitCommand.errorReadingLogFile", new Object[] { fileName })); // NOI18N } finally { if (reader != null) { try { reader.close(); } catch (IOException exc) { } } } return buffer.toString(); } /** * Resets all switches in the command. After calling this method, the command should have no switches defined and should behave * defaultly. */ @Override public void resetCVSCommand() { setMessage(null); setRecursive(true); setForceCommit(false); setLogMessageFromFile(null); setNoModuleProgram(false); setToRevisionOrBranch(null); } /** * Returns the arguments of the command in the command-line style. Similar to getCVSCommand() however without the files and command's * name. */ @Override public String getCVSArguments() { StringBuffer toReturn = new StringBuffer(); if (!isRecursive()) { toReturn.append("-l "); // NOI18N } if (isForceCommit()) { toReturn.append("-f "); // NOI18N if (isRecursive()) { toReturn.append("-R "); } } if (isNoModuleProgram()) { toReturn.append("-n "); // NOI18N } if (getToRevisionOrBranch() != null) { toReturn.append("-r "); // NOI18N toReturn.append(getToRevisionOrBranch() + " "); // NOI18N } if (getLogMessageFromFile() != null) { toReturn.append("-F "); // NOI18N toReturn.append(getLogMessageFromFile()); toReturn.append(" "); // NOI18N } if (getMessage() != null) { toReturn.append("-m \""); // NOI18N toReturn.append(getMessage()); toReturn.append("\" "); // NOI18N } return toReturn.toString(); } }