blob: 818740afc128e86f553510f96bb93d4d1979efab [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2012 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.ui.operations;
import java.io.*;
import java.util.*;
import org.eclipse.compare.patch.WorkspacePatcherUI;
import org.eclipse.core.resources.*;
import org.eclipse.core.resources.mapping.ResourceMapping;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.client.*;
import org.eclipse.team.internal.ccvs.core.client.Command.LocalOption;
import org.eclipse.team.internal.ccvs.core.client.listeners.DiffListener;
import org.eclipse.team.internal.ccvs.core.connection.CVSCommunicationException;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
import org.eclipse.team.internal.ccvs.ui.CVSUIMessages;
import org.eclipse.team.internal.ccvs.ui.Policy;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.statushandlers.*;
public abstract class DiffOperation extends SingleCommandOperation {
private static final int UNIFIED_FORMAT = 0;
private static final int CONTEXT_FORMAT = 1;
private static final int STANDARD_FORMAT = 2;
protected boolean isMultiPatch;
protected boolean includeFullPathInformation;
protected PrintStream stream;
protected IPath patchRoot;
protected boolean patchHasContents;
protected boolean patchHasNewFiles;
/* see bug 116427 */
private Object destination = null;
/* see bug 159894 */
private class CustomizableEOLPrintStream extends PrintStream{
private boolean error = false;
private String defaultLineEnding = "\n"; //$NON-NLS-1$
public CustomizableEOLPrintStream(PrintStream openStream) {
super(openStream);
if(CVSProviderPlugin.getPlugin().isUsePlatformLineend()){
defaultLineEnding = System.getProperty("line.separator"); //$NON-NLS-1$
}
}
@Override
public boolean checkError() {
return error || super.checkError();
}
@Override
public void println() {
try{
write(defaultLineEnding.getBytes());
} catch (IOException e){
error = true;
}
}
@Override
public void println(boolean x) {
print(x);
println();
}
@Override
public void println(char x) {
print(x);
println();
}
@Override
public void println(char[] x) {
print(x);
println();
}
@Override
public void println(double x) {
print(x);
println();
}
@Override
public void println(float x) {
print(x);
println();
}
@Override
public void println(int x) {
print(x);
println();
}
@Override
public void println(long x) {
print(x);
println();
}
@Override
public void println(Object x) {
print(x);
println();
}
@Override
public void println(String x) {
print(x);
println();
}
}
public DiffOperation(IWorkbenchPart part, ResourceMapping[] mappings, LocalOption[] options, boolean isMultiPatch, boolean includeFullPathInformation, IPath patchRoot, Object destination) {
super(part, mappings, options);
this.isMultiPatch = isMultiPatch;
this.includeFullPathInformation=includeFullPathInformation;
this.patchRoot=patchRoot;
this.patchHasContents=false;
this.patchHasNewFiles=false;
this.destination = destination;
}
@Override
protected boolean shouldRun(){
if (super.shouldRun() == false){
return false;
}
Job[] jobs = Job.getJobManager().find(destination);
if(jobs.length != 0){
MessageDialog question = new MessageDialog(getShell(),
CVSUIMessages.DiffOperation_CreatePatchConflictTitle, null,
NLS.bind(CVSUIMessages.DiffOperation_CreatePatchConflictMessage, destination.toString()),
MessageDialog.QUESTION,
new String[]{IDialogConstants.YES_LABEL, IDialogConstants.NO_LABEL},
1);
if(question.open() == 0){
Job.getJobManager().cancel(destination);
} else {
return false;
}
}
return true;
}
@Override
public void execute(IProgressMonitor monitor) throws CVSException, InterruptedException {
try {
stream = new CustomizableEOLPrintStream(openStream());
if (isMultiPatch){
stream.println(WorkspacePatcherUI.getWorkspacePatchHeader());
}
super.execute(monitor);
} finally {
if (stream != null) {
stream.close();
}
}
}
/**
* Open and return a stream for the diff output.
* @return a stream for the diff output
*/
protected abstract PrintStream openStream() throws CVSException;
private static Comparator COMPARATOR = new Comparator() {
private int compare(IResource r1, IResource r2) {
return r1.getFullPath().toString().compareTo(r2.getFullPath().toString());
}
@Override
public int compare(Object o1, Object o2) {
IResource r1 = null;
IResource r2 = null;
if (o1 instanceof ICVSResource) {
r1 = ((ICVSResource)o1).getIResource();
} else {
r1 = (IResource)o1;
}
if (o2 instanceof ICVSResource) {
r2 = ((ICVSResource)o2).getIResource();
} else {
r2 = (IResource)o2;
}
return compare(r1, r2);
}
};
@Override
protected void execute(CVSTeamProvider provider, IResource[] resources, boolean recurse, IProgressMonitor monitor) throws CVSException, InterruptedException {
//add this project to the total projects encountered
final HashSet<ICVSFile> newFiles = new HashSet<>(); // need HashSet to guard for duplicate entries
final HashSet<IResource> existingFiles = new HashSet<>(); // need HashSet to guard for duplicate entries
monitor.beginTask(null,100);
final IProgressMonitor subMonitor = Policy.subMonitorFor(monitor,10);
for (IResource resource : resources) {
ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(resource);
cvsResource.accept(new ICVSResourceVisitor() {
@Override
public void visitFile(ICVSFile file) throws CVSException {
if (!(file.isIgnored())) {
if (!file.isManaged() || file.getSyncInfo().isAdded() ){
//this is a new file
if (file.exists())
newFiles.add(file);
}else if (file.isModified(subMonitor)){
existingFiles.add(file.getIResource());
}
}
}
@Override
public void visitFolder(ICVSFolder folder) throws CVSException {
// Even if we are not supposed to recurse we still need to go into
// the root directory.
if (!folder.exists() || folder.isIgnored() ) {
return;
}
folder.acceptChildren(this);
}
}, recurse);
}
final SortedSet<Object> allFiles = new TreeSet<>(COMPARATOR);
allFiles.addAll(existingFiles);
allFiles.addAll(newFiles);
subMonitor.done();
//Check options
//Append our diff output to the server diff output.
// Our diff output includes new files and new files in new directories.
int format = STANDARD_FORMAT;
LocalOption[] localoptions = getLocalOptions(recurse);
for (LocalOption option : localoptions) {
if (option.equals(Diff.UNIFIED_FORMAT) ||
isMultiPatch) {
format = UNIFIED_FORMAT;
} else if (option.equals(Diff.CONTEXT_FORMAT)) {
format = CONTEXT_FORMAT;
}
}
boolean haveAddedProjectHeader=false;
if (!existingFiles.isEmpty()){
if (isMultiPatch && !haveAddedProjectHeader){
haveAddedProjectHeader=true;
IProject project=resources[0].getProject();
stream.println(WorkspacePatcherUI.getWorkspacePatchProjectHeader(project));
}
}
if (!newFiles.isEmpty() && Diff.INCLUDE_NEWFILES.isElementOf(localoptions)){
//Set new file to flag to let us know that we have added something to the current patch
patchHasNewFiles=true;
if (isMultiPatch &&!haveAddedProjectHeader){
haveAddedProjectHeader=true;
IProject project=resources[0].getProject();
stream.println(WorkspacePatcherUI.getWorkspacePatchProjectHeader(project));
}
}
List<Object> existingFilesSubList = new ArrayList<>();
for (Iterator iter = allFiles.iterator(); iter.hasNext();) {
Object file = iter.next();
if (existingFiles.contains(file)) {
existingFilesSubList.add(file);
} else if (newFiles.contains(file)){
addExistingFilesSubListToDiff(provider, existingFilesSubList, recurse, monitor, existingFiles.size());
ICVSFile cvsFile = (ICVSFile) file;
addFileToDiff(getNewFileRoot(cvsFile), cvsFile,stream,format);
}
}
addExistingFilesSubListToDiff(provider, existingFilesSubList, recurse, monitor, existingFiles.size());
monitor.done();
}
private void addExistingFilesSubListToDiff(CVSTeamProvider provider, Collection subList, boolean recurse, IProgressMonitor monitor, int existingFilesTotal) throws InterruptedException {
if (!subList.isEmpty()) {
int ticks = 90 * subList.size() / existingFilesTotal;
try{
super.execute(provider, (IResource[]) subList.toArray(new IResource[subList.size()]), recurse, Policy.subMonitorFor(monitor, ticks));
} catch(CVSCommunicationException ex){ // see bug 123430
StatusAdapter adapter = new StatusAdapter(ex.getStatus());
adapter.setProperty(
IStatusAdapterConstants.TITLE_PROPERTY,
CVSUIMessages.DiffOperation_ErrorsOccurredWhileCreatingThePatch);
StatusManager.getManager().handle(adapter,
StatusManager.SHOW | StatusManager.NONE);
} catch (CVSException ex) {
handleCVSException(ex);
}
subList.clear();
}
}
/**
* Checks if the exception contain a status that has to be shown to the
* user. If yes, the method shows the dialog.
*
* @param ex exception to handle
*/
private void handleCVSException(CVSException ex) {
IStatus status = ex.getStatus();
List<IStatus> toShow = new ArrayList<>();
IStatus children[] = status.getChildren();
boolean may = true;
for (IStatus child : children) {
// ignore all errors except those found by DiffListener
if (child.getCode() == CVSStatus.BINARY_FILES_DIFFER || child.getCode() == CVSStatus.PROTOCOL_ERROR || child.getCode() == CVSStatus.ERROR_LINE) {
toShow.add(child);
if (child.getCode() == CVSStatus.BINARY_FILES_DIFFER) {
// the patch does not contain some changes for sure
may = false;
}
}
}
if (toShow.size() > 0) {
String msg = may ? CVSUIMessages.DiffOperation_ThePatchMayNotContainAllTheChanges
: CVSUIMessages.DiffOperation_ThePatchDoesNotContainAllTheChanges;
StatusAdapter adapter = new StatusAdapter(
new MultiStatus(
CVSProviderPlugin.ID,
CVSStatus.SERVER_ERROR,
toShow.toArray(new IStatus[toShow.size()]),
CVSUIMessages.DiffOperation_ErrorsOccurredWhileCreatingThePatch,
null));
adapter.setProperty(IStatusAdapterConstants.TITLE_PROPERTY, msg);
StatusManager.getManager().handle(adapter,
StatusManager.SHOW | StatusManager.LOG);
}
}
private ICVSFolder getNewFileRoot(ICVSFile cvsFile) {
ICVSFolder patchRootFolder = getPatchRootFolder();
if (patchRootFolder != null)
return patchRootFolder;
return CVSWorkspaceRoot.getCVSFolderFor(cvsFile.getIResource().getProject());
}
@Override
protected IStatus executeCommand(Session session, CVSTeamProvider provider, ICVSResource[] resources, boolean recurse, IProgressMonitor monitor) throws CVSException, InterruptedException {
DiffListener diffListener = new DiffListener(stream);
IStatus status = Command.DIFF.execute(session,
Command.NO_GLOBAL_OPTIONS,
getLocalOptions(recurse),
resources,
diffListener,
monitor);
//Once any run of the Diff commands reports that it has written something to the stream, the patch
//in its entirety is considered non-empty - until then keep trying to set the flag.
if (!patchHasContents)
patchHasContents = diffListener.wroteToStream();
return status;
}
@Override
protected String getTaskName(CVSTeamProvider provider) {
return NLS.bind(CVSUIMessages.DiffOperation_0, new String[]{provider.getProject().getName()});
}
@Override
protected String getTaskName() {
return CVSUIMessages.DiffOperation_1;
}
@Override
Map getProviderTraversalMapping(IProgressMonitor monitor) throws CoreException {
Map providerTraversal = super.getProviderTraversalMapping(monitor);
SortedMap result = new TreeMap((o1, o2) -> {
CVSTeamProvider p1 = (CVSTeamProvider) o1;
CVSTeamProvider p2 = (CVSTeamProvider) o2;
return COMPARATOR.compare(p1.getProject(), p2.getProject());
});
result.putAll(providerTraversal);
return result;
}
private void addFileToDiff(ICVSFolder patchRoot, ICVSFile file, PrintStream printStream, int format) throws CVSException {
String nullFilePrefix = ""; //$NON-NLS-1$
String newFilePrefix = ""; //$NON-NLS-1$
String positionInfo = ""; //$NON-NLS-1$
String linePrefix = ""; //$NON-NLS-1$
String pathString=""; //$NON-NLS-1$
//get the path string for this file
pathString= file.getRelativePath(patchRoot);
int lines = 0;
BufferedReader fileReader = new BufferedReader(new InputStreamReader(file.getContents()));
try {
while (fileReader.readLine() != null) {
lines++;
}
} catch (IOException e) {
throw CVSException.wrapException(file.getIResource(), NLS.bind(CVSUIMessages.DiffOperation_ErrorAddingFileToDiff, new String[] { pathString }), e);
} finally {
try {
fileReader.close();
} catch (IOException e1) {
//ignore
}
}
// Ignore empty files
if (lines == 0)
return;
switch (format) {
case UNIFIED_FORMAT:
nullFilePrefix = "--- "; //$NON-NLS-1$
newFilePrefix = "+++ "; //$NON-NLS-1$
positionInfo = "@@ -0,0 +1," + lines + " @@" ; //$NON-NLS-1$ //$NON-NLS-2$
linePrefix = "+"; //$NON-NLS-1$
break;
case CONTEXT_FORMAT :
nullFilePrefix = "*** "; //$NON-NLS-1$
newFilePrefix = "--- "; //$NON-NLS-1$
positionInfo = "--- 1," + lines + " ----"; //$NON-NLS-1$ //$NON-NLS-2$
linePrefix = "+ "; //$NON-NLS-1$
break;
default :
positionInfo = "0a1," + lines; //$NON-NLS-1$
linePrefix = "> "; //$NON-NLS-1$
break;
}
fileReader = new BufferedReader(new InputStreamReader(file.getContents()));
try {
printStream.println("Index: " + pathString); //$NON-NLS-1$
printStream.println("==================================================================="); //$NON-NLS-1$
printStream.println("RCS file: " + pathString); //$NON-NLS-1$
printStream.println("diff -N " + pathString); //$NON-NLS-1$
if (format != STANDARD_FORMAT) {
printStream.println(nullFilePrefix + "/dev/null 1 Jan 1970 00:00:00 -0000"); //$NON-NLS-1$
// Technically this date should be the local file date but nobody really cares.
printStream.println(newFilePrefix + pathString + " 1 Jan 1970 00:00:00 -0000"); //$NON-NLS-1$
}
if (format == CONTEXT_FORMAT) {
printStream.println("***************"); //$NON-NLS-1$
printStream.println("*** 0 ****"); //$NON-NLS-1$
}
printStream.println(positionInfo);
for (int i = 0; i < lines - 1; i++) {
printStream.print(linePrefix);
printStream.println(fileReader.readLine());
}
printStream.print(linePrefix);
readLastLine(fileReader, printStream);
} catch (IOException e) {
throw CVSException.wrapException(file.getIResource(), NLS.bind(CVSUIMessages.DiffOperation_ErrorAddingFileToDiff, new String[] { pathString }), e);
} finally {
try {
fileReader.close();
} catch (IOException e1) {
}
}
}
// based on org.eclipse.compare.internal.core.patch.LineReader.readLine()
private void readLastLine(BufferedReader reader, PrintStream printStream)
throws IOException {
boolean sawCRorLF = false;
boolean sawEOF = false;
// TODO: hardcoded, set to the same value as initially in LineReader
boolean ignoreSingleCR = false;
while (!sawEOF) {
int c = reader.read();
if (c == -1) {
sawEOF = true;
break;
}
printStream.print((char) c);
if (c == '\n') {
sawCRorLF = true;
break;
}
if (c == '\r') {
sawCRorLF = true;
c = reader.read();
if (c == -1) {
sawEOF = true;
break; // EOF
}
if (c != '\n') {
if (ignoreSingleCR) {
sawCRorLF = false;
printStream.print((char) c);
continue;
}
} else { // '\n'
printStream.print((char) c);
}
break;
}
}
if (!sawCRorLF) {
printStream.println();
printStream.println("\\ No newline at end of file"); //$NON-NLS-1$
}
}
public void setStream(PrintStream stream) {
this.stream = new CustomizableEOLPrintStream(stream);
}
protected void reportEmptyDiff() {
StatusAdapter adapter = new StatusAdapter(new Status(IStatus.INFO,
CVSProviderPlugin.ID,
CVSUIMessages.GenerateCVSDiff_noDiffsFoundMsg));
adapter.setProperty(IStatusAdapterConstants.TITLE_PROPERTY,
CVSUIMessages.GenerateCVSDiff_noDiffsFoundTitle);
StatusManager.getManager().handle(adapter,
StatusManager.SHOW);
}
@Override
protected ICVSFolder getLocalRoot(CVSTeamProvider provider) throws CVSException {
ICVSFolder root = getPatchRootFolder();
if (root != null)
return root;
return super.getLocalRoot(provider);
}
private ICVSFolder getPatchRootFolder() {
if (!isMultiPatch &&
!includeFullPathInformation){
//Check to see if the selected patchRoot has enough segments to consider it a folder/resource
//if not just get the project
IResource patchFolder = null;
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
if (patchRoot.segmentCount() > 1){
patchFolder = root.getFolder(patchRoot);
} else {
patchFolder = root.getProject(patchRoot.toString());
}
ICVSResource cvsResource = CVSWorkspaceRoot.getCVSResourceFor(patchFolder);
if (!cvsResource.isFolder()) {
cvsResource = cvsResource.getParent();
}
return (ICVSFolder) cvsResource;
}
return null;
}
@Override
public boolean consultModelsForMappings() {
return false;
}
@Override
public boolean belongsTo(Object family){
if(family != null && family.equals(destination))
return true;
return super.belongsTo(family);
}
}