| /******************************************************************************* |
| * Copyright (c) 2000, 2014 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.team.internal.ccvs.core.client.listeners; |
| |
| import java.util.Map; |
| |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.team.internal.ccvs.core.*; |
| import org.eclipse.team.internal.ccvs.core.CVSException; |
| import org.eclipse.team.internal.ccvs.core.CVSProviderPlugin; |
| import org.eclipse.team.internal.ccvs.core.CVSStatus; |
| import org.eclipse.team.internal.ccvs.core.ICVSFile; |
| import org.eclipse.team.internal.ccvs.core.ICVSFolder; |
| import org.eclipse.team.internal.ccvs.core.ICVSRepositoryLocation; |
| import org.eclipse.team.internal.ccvs.core.client.CommandOutputListener; |
| import org.eclipse.team.internal.ccvs.core.client.Update; |
| import org.eclipse.team.internal.ccvs.core.util.Util; |
| |
| public class UpdateListener extends CommandOutputListener { |
| |
| // Pattern matchers |
| private static ServerMessageLineMatcher MERGED_BINARY_FILE_LINE_1; |
| private static ServerMessageLineMatcher MERGED_BINARY_FILE_LINE_2; |
| |
| // Pattern Variables |
| private static final String REVISION_VARIABLE_NAME = "revision"; //$NON-NLS-1$ |
| private static final String LOCAL_FILE_PATH_VARIABLE_NAME = "localFilePath"; //$NON-NLS-1$ |
| private static final String BACKUP_FILE_VARIABLE_NAME = "backupFile"; //$NON-NLS-1$ |
| |
| static { |
| try { |
| String line1 = "revision " //$NON-NLS-1$ |
| + Util.getVariablePattern(IMessagePatterns.REVISION_PATTERN, REVISION_VARIABLE_NAME) |
| + " from repository is now in " //$NON-NLS-1$ |
| + Util.getVariablePattern(IMessagePatterns.FILE_PATH_PATTERN, LOCAL_FILE_PATH_VARIABLE_NAME); |
| MERGED_BINARY_FILE_LINE_1 = new ServerMessageLineMatcher( |
| line1, |
| new String[] {REVISION_VARIABLE_NAME, LOCAL_FILE_PATH_VARIABLE_NAME}); |
| String line2 = "file from working directory is now in " //$NON-NLS-1$ |
| + Util.getVariablePattern(IMessagePatterns.REVISION_PATTERN, BACKUP_FILE_VARIABLE_NAME); |
| MERGED_BINARY_FILE_LINE_2 = new ServerMessageLineMatcher( |
| line2, |
| new String[] {BACKUP_FILE_VARIABLE_NAME}); |
| |
| } catch (CVSException e) { |
| // Shouldn't happen |
| CVSProviderPlugin.log(e); |
| } |
| } |
| |
| IUpdateMessageListener updateMessageListener; |
| boolean merging = false; |
| boolean mergingBinary = false; |
| String mergedBinaryFileRevision, mergedBinaryFilePath; |
| |
| public UpdateListener(IUpdateMessageListener updateMessageListener) { |
| this.updateMessageListener = updateMessageListener; |
| } |
| |
| public IStatus messageLine(String line, ICVSRepositoryLocation location, ICVSFolder commandRoot, |
| IProgressMonitor monitor) { |
| mergingBinary = false; |
| if (updateMessageListener == null) return OK; |
| if(line.startsWith("Merging differences")) { //$NON-NLS-1$ |
| merging = true; |
| } else if(line.indexOf(' ')==1) { |
| // We have a message that indicates the type of update. The possible messages are |
| // defined by the prefix constants MLP_*. |
| String path = line.substring(2); |
| char changeType = line.charAt(0); |
| |
| // calculate change type |
| int type = 0; |
| switch(changeType) { |
| case 'A': type = Update.STATE_ADDED_LOCAL; break; // new file locally that was added but not comitted to server yet |
| case '?': type = Update.STATE_UNKOWN; break; // new file locally but not added to server |
| case 'U': type = Update.STATE_REMOTE_CHANGES; break; // remote changes to an unmodified local file |
| case 'R': type = Update.STATE_DELETED; break; // removed locally but still exists on the server |
| case 'M': type = Update.STATE_MODIFIED; break; // modified locally |
| case 'C': type = Update.STATE_CONFLICT; break; // modified locally and on the server but cannot be auto-merged |
| case 'D': type = Update.STATE_DELETED; break; // deleted locally but still exists on server |
| default: type = Update.STATE_NONE; |
| } |
| |
| if (merging) { |
| // If we are merging the modified prefix is used both to show merges and |
| // local changes. We have to detect this case and use a more specific change |
| // type. |
| if (type == Update.STATE_MODIFIED) |
| type = Update.STATE_MERGEABLE_CONFLICT; |
| merging = false; |
| } |
| updateMessageListener.fileInformation(type, commandRoot, path); |
| } |
| return OK; |
| } |
| |
| /** |
| * This handler is used by the RemoteResource hierarchy to retrieve E messages |
| * from the CVS server in order to determine the folders contained in a parent folder. |
| * |
| * WARNING: This class parses the message output to determine the state of files in the |
| * repository. Unfortunately, these messages seem to be customizable on a server by server basis. |
| * |
| * Here's a list of responses we expect in various situations: |
| * |
| * Directory exists remotely: |
| * cvs server: Updating folder1/folder2 |
| * Directory doesn't exist remotely: |
| * cvs server: skipping directory folder1/folder2 |
| * New (or unknown) remote directory |
| * cvs server: New Directory folder1/folder2 |
| * File removed remotely |
| * cvs server: folder1/file.ext is no longer in the repository |
| * cvs server: warning: folder1/file.ext is not (any longer) pertinent |
| * Locally added file was added remotely as well |
| * cvs server: conflict: folder/file.ext created independently by second party |
| * File removed locally and modified remotely |
| * cvs server: conflict: removed file.txt was modified by second party |
| * File modified locally but removed remotely |
| * cvs server: conflict: file.txt is modified but no longer in the repository |
| * Ignored Messages |
| * cvs server: cannot open directory ... |
| * cvs server: nothing known about ... |
| * Tag error that really means there are no files in a directory |
| * cvs [server aborted]: no such tag |
| * Merge contained conflicts |
| * rcsmerge: warning: conflicts during merge |
| * Binary file conflict |
| * cvs server: nonmergeable file needs merge |
| * cvs server: revision 1.4 from repository is now in a1/a2/test |
| * cvs server: file from working directory is now in .#test.1.3 |
| */ |
| public IStatus errorLine(String line, ICVSRepositoryLocation location, ICVSFolder commandRoot, |
| IProgressMonitor monitor) { |
| |
| try { |
| // Reset flag globally here because we have to many exit points |
| boolean wasMergingBinary = mergingBinary; |
| mergingBinary = false; |
| String serverMessage = getServerMessage(line, location); |
| if (serverMessage != null) { |
| // Strip the prefix from the line |
| String message = serverMessage; |
| if (message.startsWith("Updating")) { //$NON-NLS-1$ |
| if (updateMessageListener != null) { |
| String path = message.substring(9); |
| updateMessageListener.directoryInformation(commandRoot, path, false); |
| } |
| return OK; |
| } else if (message.startsWith("skipping directory")) { //$NON-NLS-1$ |
| if (updateMessageListener != null) { |
| String path = message.substring(18).trim(); |
| updateMessageListener.directoryDoesNotExist(commandRoot, path); |
| } |
| return OK; |
| } else if (message.startsWith("New directory")) { //$NON-NLS-1$ |
| if (updateMessageListener != null) { |
| String path = message.substring(15, message.lastIndexOf('\'')); |
| updateMessageListener.directoryInformation(commandRoot, path, true); |
| } |
| return OK; |
| } else if (message.endsWith("is no longer in the repository")) { //$NON-NLS-1$ |
| if (updateMessageListener != null) { |
| String filename = message.substring(0, message.length() - 31); |
| filename = stripQuotes(filename); |
| updateMessageListener.fileDoesNotExist(commandRoot, filename); |
| } |
| return OK; |
| } else if (message.startsWith("conflict:")) { //$NON-NLS-1$ |
| /* |
| * We can get the following conflict warnings |
| * cvs server: conflict: folder/file.ext created independently by second party |
| * cvs server: conflict: removed file.txt was modified by second party |
| * cvs server: conflict: file.txt is modified but no longer in the repository |
| * If we get the above line, we have conflicting additions or deletions and we can expect a server error. |
| * We still get "C foler/file.ext" so we don't need to do anything else (except in the remotely deleted case) |
| */ |
| if (updateMessageListener != null) { |
| if (message.endsWith("is modified but no longer in the repository")) { //$NON-NLS-1$ |
| // The "C foler/file.ext" will come after this so if whould be ignored! |
| String filename = message.substring(10, message.length() - 44); |
| filename = stripQuotes(filename); |
| updateMessageListener.fileDoesNotExist(commandRoot, filename); |
| } |
| } |
| return new CVSStatus(IStatus.WARNING, CVSStatus.CONFLICT, line, commandRoot); |
| } else if (message.startsWith("warning:")) { //$NON-NLS-1$ |
| /* |
| * We can get the following conflict warnings |
| * cvs server: warning: folder1/file.ext is not (any longer) pertinent |
| * If we get the above line, we have local changes to a remotely deleted file. |
| */ |
| if (updateMessageListener != null) { |
| if (message.endsWith("is not (any longer) pertinent")) { //$NON-NLS-1$ |
| String filename = message.substring(9, message.length() - 30); |
| updateMessageListener.fileDoesNotExist(commandRoot, filename); |
| } |
| } |
| return new CVSStatus(IStatus.WARNING, CVSStatus.CONFLICT, line, commandRoot); |
| } else if (message.startsWith("conflicts")) { //$NON-NLS-1$ |
| // This line is info only. The server doesn't report an error. |
| return new CVSStatus(IStatus.INFO, CVSStatus.CONFLICT, line, commandRoot); |
| } else if (message.startsWith("nonmergeable file needs merge")) { //$NON-NLS-1$ |
| mergingBinary = true; |
| mergedBinaryFileRevision = null; |
| mergedBinaryFilePath = null; |
| return OK; |
| } else if (wasMergingBinary) { |
| Map variables = MERGED_BINARY_FILE_LINE_1.processServerMessage(message); |
| if (variables != null) { |
| mergedBinaryFileRevision = (String)variables.get(REVISION_VARIABLE_NAME); |
| mergedBinaryFilePath = (String)variables.get(LOCAL_FILE_PATH_VARIABLE_NAME); |
| mergingBinary = true; |
| return OK; |
| } |
| variables = MERGED_BINARY_FILE_LINE_2.processServerMessage(message); |
| if (variables != null) { |
| String backupFile = (String)variables.get(BACKUP_FILE_VARIABLE_NAME); |
| try { |
| if (mergedBinaryFileRevision != null && mergedBinaryFilePath != null) { |
| ICVSFile file = commandRoot.getFile(mergedBinaryFilePath); |
| IResource resource = file.getIResource(); |
| if (resource != null) { |
| return new CVSStatus(IStatus.ERROR, CVSStatus.UNMEGERED_BINARY_CONFLICT, |
| NLS.bind(CVSMessages.UpdateListener_0, (new Object[] { |
| resource.getFullPath().toString(), |
| mergedBinaryFileRevision, |
| resource.getFullPath().removeLastSegments(1).append(backupFile).toString()})), commandRoot); |
| } |
| } |
| } catch (CVSException e1) { |
| CVSProviderPlugin.log(e1); |
| } |
| return OK; |
| } |
| } |
| |
| // Fallthrough case for "cvs server" messages |
| if (!message.startsWith("cannot open directory") //$NON-NLS-1$ |
| && !message.startsWith("nothing known about")) { //$NON-NLS-1$ |
| return super.errorLine(line, location, commandRoot, monitor); |
| } |
| } else { |
| String serverAbortedMessage = getServerAbortedMessage(line, location); |
| if (serverAbortedMessage != null) { |
| // Strip the prefix from the line |
| String message = serverAbortedMessage; |
| if (message.startsWith("no such tag")) { //$NON-NLS-1$ |
| // This is reported from CVS when a tag is used on the update there are no files in the directory |
| // To get the folders, the update request should be re-issued for HEAD |
| return new CVSStatus(IStatus.WARNING, CVSStatus.NO_SUCH_TAG, line, commandRoot); |
| } else if (message.startsWith("Numeric join") && message.endsWith("may not contain a date specifier")) { //$NON-NLS-1$ //$NON-NLS-2$ |
| // This error indicates a join failed because a date tag was used |
| return super.errorLine(line, location, commandRoot, monitor); |
| } else { |
| return super.errorLine(line, location, commandRoot, monitor); |
| } |
| } else if (line.equals("rcsmerge: warning: conflicts during merge")) { //$NON-NLS-1$ |
| // There were conflicts in the merge |
| return new CVSStatus(IStatus.WARNING, CVSStatus.CONFLICT, line, commandRoot); |
| } |
| } |
| } catch (StringIndexOutOfBoundsException e) { |
| // Something went wrong in the parsing of the message. |
| // Return a status indicating the problem |
| if (Policy.DEBUG) { |
| System.out.println("Error parsing E line: " + line); //$NON-NLS-1$ |
| } |
| return new CVSStatus(IStatus.ERROR, CVSStatus.ERROR_LINE_PARSE_FAILURE, line, commandRoot); |
| } |
| return super.errorLine(line, location, commandRoot, monitor); |
| } |
| |
| private String stripQuotes(String filename) { |
| // CVS version 12 fix - filenames are returned inside quotes |
| // Fixes bug 49056 |
| if (filename.startsWith("`") && filename.endsWith("'")) //$NON-NLS-1$ //$NON-NLS-2$ |
| filename = filename.substring(1,filename.length()-1); |
| return filename; |
| } |
| |
| } |