blob: 97d82aeebe9fa4c70e83785d11e21569d4a4bbbd [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2006 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.util.*;
import org.eclipse.compare.CompareUI;
import org.eclipse.core.runtime.*;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Display;
import org.eclipse.team.core.TeamException;
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.RDiffSummaryListener;
import org.eclipse.team.internal.ccvs.core.resources.*;
import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo;
import org.eclipse.team.internal.ccvs.ui.*;
import org.eclipse.team.internal.ccvs.ui.Policy;
import org.eclipse.team.internal.ui.TeamUIPlugin;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
/**
* Compare the two versions of given remote folders obtained from the two tags specified.
*/
public class RemoteCompareOperation extends RemoteOperation {
private CompareTreeBuilder builder;
private CVSTag left, right;
/**
* Helper class for builder and comparing the resource trees
*/
public static class CompareTreeBuilder implements RDiffSummaryListener.IFileDiffListener {
private ICVSRepositoryLocation location;
private RemoteFolderTree leftTree, rightTree;
private CVSTag left, right;
public CompareTreeBuilder(ICVSRepositoryLocation location, CVSTag left, CVSTag right) {
this.left = left;
this.right = right;
this.location = location;
reset();
}
public RemoteFolderTree getLeftTree() {
return leftTree;
}
public RemoteFolderTree getRightTree() {
return rightTree;
}
/**
* Reset the builder to prepare for a new build
*/
public void reset() {
leftTree = new RemoteFolderTree(null, location, ICVSRemoteFolder.REPOSITORY_ROOT_FOLDER_NAME, left);
leftTree.setChildren(new ICVSRemoteResource[0]);
rightTree = new RemoteFolderTree(null, location, ICVSRemoteFolder.REPOSITORY_ROOT_FOLDER_NAME, right);
rightTree.setChildren(new ICVSRemoteResource[0]);
}
/**
* Cache the contents for the files that are about to be compares
* @throws CVSException
*/
public void cacheContents(IProgressMonitor monitor) throws CVSException {
String[] overlappingFilePaths = getOverlappingFilePaths();
if (overlappingFilePaths.length > 0) {
monitor.beginTask(null, 100);
fetchFileContents(leftTree, overlappingFilePaths, Policy.subMonitorFor(monitor, 50));
fetchFileContents(rightTree, overlappingFilePaths, Policy.subMonitorFor(monitor, 50));
monitor.done();
}
}
/**
* Open the comparison in a compare editor
*/
public void openCompareEditor(final IWorkbenchPage page, final String title, final String toolTip) {
if (leftTree == null || rightTree == null) return;
Display.getDefault().asyncExec(() -> CompareUI.openCompareEditorOnPage(new CVSCompareEditorInput(title,
toolTip, new ResourceEditionNode(leftTree), new ResourceEditionNode(rightTree)), page));
}
/**
* Add the predecessor to the left tree and the remote to the right tree.
* @param predecessor
* @param remote
*/
public void addToTrees(ICVSRemoteFile predecessor, ICVSRemoteFile remote) {
if (remote != null) {
try {
Path filePath = new Path(null, remote.getRepositoryRelativePath());
addFile(rightTree, right, filePath, remote.getRevision());
getFolder(leftTree, left, filePath.removeLastSegments(1), Path.EMPTY);
} catch (TeamException e) {
CVSUIPlugin.log(e);
}
}
if (predecessor != null) {
try {
Path filePath = new Path(null, predecessor.getRepositoryRelativePath());
addFile(leftTree, left, filePath, predecessor.getRevision());
getFolder(rightTree, right, filePath.removeLastSegments(1), Path.EMPTY);
} catch (TeamException e) {
CVSUIPlugin.log(e);
}
}
}
private void addFile(RemoteFolderTree tree, CVSTag tag, Path filePath, String revision) throws CVSException {
RemoteFolderTree parent = (RemoteFolderTree)getFolder(tree, tag, filePath.removeLastSegments(1), Path.EMPTY);
String name = filePath.lastSegment();
ICVSRemoteFile file = new RemoteFile(parent, 0, name, revision, null, getTag(revision, tag));
addChild(parent, file);
}
private CVSTag getTag(String revision, CVSTag tag) {
if (tag == null) {
tag = new CVSTag(revision, CVSTag.VERSION);
}
return tag;
}
/*
* Get the folder at the given path in the given tree, creating any missing folders as needed.
*/
private ICVSRemoteFolder getFolder(RemoteFolderTree tree, CVSTag tag, IPath remoteFolderPath, IPath parentPath) throws CVSException {
if (remoteFolderPath.segmentCount() == 0) return tree;
String name = remoteFolderPath.segment(0);
ICVSResource child;
IPath childPath = parentPath.append(name);
if (tree.childExists(name)) {
child = tree.getChild(name);
} else {
child = new RemoteFolderTree(tree, tree.getRepository(), childPath.toString(), tag);
((RemoteFolderTree)child).setChildren(new ICVSRemoteResource[0]);
addChild(tree, (ICVSRemoteResource)child);
}
return getFolder((RemoteFolderTree)child, tag, remoteFolderPath.removeFirstSegments(1), childPath);
}
private void addChild(RemoteFolderTree tree, ICVSRemoteResource resource) {
ICVSRemoteResource[] children = tree.getChildren();
ICVSRemoteResource[] newChildren;
if (children == null) {
newChildren = new ICVSRemoteResource[] { resource };
} else {
newChildren = new ICVSRemoteResource[children.length + 1];
System.arraycopy(children, 0, newChildren, 0, children.length);
newChildren[children.length] = resource;
}
tree.setChildren(newChildren);
}
@Override
public void fileDiff(String remoteFilePath, String leftRevision, String rightRevision) {
try {
addFile(rightTree, right, new Path(null, remoteFilePath), rightRevision);
} catch (CVSException e) {
CVSUIPlugin.log(e);
}
try {
addFile(leftTree, left, new Path(null, remoteFilePath), leftRevision);
} catch (CVSException e) {
CVSUIPlugin.log(e);
}
}
@Override
public void newFile(String remoteFilePath, String rightRevision) {
try {
addFile(rightTree, right, new Path(null, remoteFilePath), rightRevision);
} catch (CVSException e) {
CVSUIPlugin.log(e);
}
}
@Override
public void deletedFile(String remoteFilePath, String leftRevision) {
// The leftRevision may be null in which case the tag is used
try {
addFile(leftTree, left, new Path(null, remoteFilePath), leftRevision);
} catch (CVSException e) {
CVSUIPlugin.log(e);
}
}
@Override
public void directory(String remoteFolderPath) {
try {
getFolder(leftTree, left, new Path(null, remoteFolderPath), Path.EMPTY);
} catch (CVSException e) {
CVSUIPlugin.log(e);
}
try {
getFolder(rightTree, right, new Path(null, remoteFolderPath), Path.EMPTY);
} catch (CVSException e) {
CVSUIPlugin.log(e);
}
}
private String[] getOverlappingFilePaths() {
String[] leftFiles = getFilePaths(leftTree);
String[] rightFiles = getFilePaths(rightTree);
Set<String> set = new HashSet<>();
for (int i = 0; i < rightFiles.length; i++) {
String rightFile = rightFiles[i];
for (int j = 0; j < leftFiles.length; j++) {
String leftFile = leftFiles[j];
if (leftFile.equals(rightFile)) {
set.add(leftFile);
}
}
}
return set.toArray(new String[set.size()]);
}
private void fetchFileContents(RemoteFolderTree tree, String[] overlappingFilePaths, IProgressMonitor monitor) throws CVSException {
FileContentCachingService.fetchFileContents(tree, overlappingFilePaths, monitor);
}
private String[] getFilePaths(RemoteFolderTree tree) {
ICVSRemoteResource[] children = tree.getChildren();
List<String> result = new ArrayList<>();
for (int i = 0; i < children.length; i++) {
ICVSRemoteResource resource = children[i];
if (resource.isContainer()) {
result.addAll(Arrays.asList(getFilePaths((RemoteFolderTree)resource)));
} else {
result.add(resource.getRepositoryRelativePath());
}
}
return result.toArray(new String[result.size()]);
}
}
public static CVSTag getTag(ICVSRemoteResource resource) throws CVSException {
CVSTag tag = null;
try {
if (resource.isContainer()) {
tag = ((ICVSRemoteFolder)resource).getTag();
} else {
ICVSRemoteFile file = (ICVSRemoteFile)resource;
String revision = file.getRevision();
if (revision.equals(ResourceSyncInfo.ADDED_REVISION)) {
ResourceSyncInfo info =file.getSyncInfo();
if (info != null) tag = info.getTag();
} else {
tag = new CVSTag(revision, CVSTag.VERSION);
}
}
} catch (TeamException e) {
throw CVSException.wrapException(e);
}
if (tag == null) tag = CVSTag.DEFAULT;
return tag;
}
public static RemoteCompareOperation create(IWorkbenchPart part, ICVSRemoteResource remoteResource, CVSTag tag) throws CVSException {
CVSTag tag0 = getTag(remoteResource);
CVSTag tag1 = tag;
if (tag0.getType() == CVSTag.DATE && tag1.getType() == CVSTag.DATE) {
if (tag0.asDate().after(tag1.asDate())) {
tag = tag0;
remoteResource = remoteResource.forTag(tag1);
}
}
return new RemoteCompareOperation(part, remoteResource, tag);
}
/**
* Compare two versions of the given remote resource.
* @param shell
* @param remoteResource the resource whose tags are being compared
* @param left the earlier tag (not null)
* @param right the later tag (not null)
*/
protected RemoteCompareOperation(IWorkbenchPart part, ICVSRemoteResource remoteResource, CVSTag tag) {
super(part, new ICVSRemoteResource[] {remoteResource});
Assert.isNotNull(tag);
this.right = tag;
try {
this.left = getTag(remoteResource);
} catch (CVSException e) {
// This shouldn't happen but log it just in case
CVSProviderPlugin.log(e);
}
if (this.left == null) {
this.left = CVSTag.DEFAULT;
}
builder = new CompareTreeBuilder(remoteResource.getRepository(), left, right);
}
/*
* This command only supports the use of a single resource
*/
private ICVSRemoteResource getRemoteResource() {
return getRemoteResources()[0];
}
@Override
protected void execute(IProgressMonitor monitor) throws CVSException {
boolean fetchContents = CVSUIPlugin.getPlugin().getPluginPreferences().getBoolean(ICVSUIConstants.PREF_CONSIDER_CONTENTS);
monitor.beginTask(getTaskName(), 50 + (fetchContents ? 100 : 0));
try {
ICVSRemoteResource resource = getRemoteResource();
IStatus status = buildTrees(resource, Policy.subMonitorFor(monitor, 50));
if (status.isOK() && fetchContents) {
builder.cacheContents(Policy.subMonitorFor(monitor, 100));
}
collectStatus(status);
openCompareEditor(builder);
} finally {
monitor.done();
}
}
/**
* This method is here to allow subclasses to override
*/
protected void openCompareEditor(CompareTreeBuilder builder) {
builder.openCompareEditor(getTargetPage(), null, null);
}
/*
* Build the two trees uses the reponses from "cvs rdiff -s ...".
*/
private IStatus buildTrees(ICVSRemoteResource resource, IProgressMonitor monitor) throws CVSException {
// Initialize the resulting trees
builder.reset();
Command.QuietOption oldOption= CVSProviderPlugin.getPlugin().getQuietness();
Session session = new Session(resource.getRepository(), builder.getLeftTree(), false);
try {
monitor.beginTask(getTaskName(), 100);
CVSProviderPlugin.getPlugin().setQuietness(Command.VERBOSE);
session.open(Policy.subMonitorFor(monitor, 10));
IStatus status = Command.RDIFF.execute(session,
Command.NO_GLOBAL_OPTIONS,
getLocalOptions(),
new ICVSResource[] { resource },
new RDiffSummaryListener(builder),
Policy.subMonitorFor(monitor, 90));
return status;
} finally {
try {
session.close();
} finally {
CVSProviderPlugin.getPlugin().setQuietness(oldOption);
}
monitor.done();
}
}
private LocalOption[] getLocalOptions() {
return new LocalOption[] {RDiff.SUMMARY, RDiff.makeTagOption(left), RDiff.makeTagOption(right)};
}
@Override
protected String getTaskName() {
return NLS.bind(CVSUIMessages.RemoteCompareOperation_0, (new Object[] {left.getName(), right.getName(), getRemoteResource().getRepositoryRelativePath()}));
}
protected IWorkbenchPage getTargetPage() {
return TeamUIPlugin.getActivePage();
}
}