blob: 257dc6d4e1b23f6098a00d4e58ef13ef95cdd874 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2011 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.mappings;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.*;
import org.eclipse.core.resources.mapping.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener;
import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.diff.*;
import org.eclipse.team.core.diff.provider.DiffTree;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.team.core.mapping.*;
import org.eclipse.team.core.mapping.provider.ResourceDiffTree;
import org.eclipse.team.core.subscribers.Subscriber;
import org.eclipse.team.core.subscribers.SubscriberScopeManager;
import org.eclipse.team.core.synchronize.SyncInfo;
import org.eclipse.team.core.synchronize.SyncInfoFilter;
import org.eclipse.team.core.synchronize.SyncInfoFilter.ContentComparisonSyncInfoFilter;
import org.eclipse.team.core.variants.IResourceVariant;
import org.eclipse.team.internal.ccvs.core.*;
import org.eclipse.team.internal.ccvs.core.client.PruneFolderVisitor;
import org.eclipse.team.internal.ccvs.core.mapping.CVSActiveChangeSetCollector;
import org.eclipse.team.internal.ccvs.core.resources.CVSWorkspaceRoot;
import org.eclipse.team.internal.ccvs.core.resources.RemoteFile;
import org.eclipse.team.internal.ccvs.ui.*;
import org.eclipse.team.internal.ccvs.ui.Policy;
import org.eclipse.team.internal.ccvs.ui.operations.CacheBaseContentsOperation;
import org.eclipse.team.internal.ccvs.ui.operations.CacheRemoteContentsOperation;
import org.eclipse.team.internal.core.mapping.GroupProgressMonitor;
import org.eclipse.team.internal.core.subscribers.ContentComparisonDiffFilter;
import org.eclipse.team.internal.core.subscribers.SubscriberDiffTreeEventHandler;
import org.eclipse.team.internal.ui.synchronize.RegexDiffFilter;
public class WorkspaceSubscriberContext extends CVSSubscriberMergeContext implements IPreferenceChangeListener {
public static final class ChangeSetSubscriberScopeManager extends SubscriberScopeManager {
private final boolean consultSets;
private ChangeSetSubscriberScopeManager(String name, ResourceMapping[] mappings, Subscriber subscriber, boolean consultModels, boolean consultSets) {
super(name, mappings, subscriber, consultModels);
this.consultSets = consultSets;
}
@Override
protected ResourceTraversal[] adjustInputTraversals(ResourceTraversal[] traversals) {
if (isConsultSets())
return ((CVSActiveChangeSetCollector)CVSUIPlugin.getPlugin().getChangeSetManager()).adjustInputTraversals(traversals);
return super.adjustInputTraversals(traversals);
}
public boolean isConsultSets() {
return consultSets;
}
}
private final int type;
public static SubscriberScopeManager createWorkspaceScopeManager(ResourceMapping[] mappings, boolean consultModels, final boolean consultChangeSets) {
return new ChangeSetSubscriberScopeManager(CVSProviderPlugin.getPlugin().getCVSWorkspaceSubscriber().getName(), mappings, CVSProviderPlugin.getPlugin().getCVSWorkspaceSubscriber(), consultModels, consultChangeSets);
}
public static SubscriberScopeManager createUpdateScopeManager(ResourceMapping[] mappings, boolean consultModels) {
return new SubscriberScopeManager(CVSProviderPlugin.getPlugin().getCVSWorkspaceSubscriber().getName(), mappings, CVSProviderPlugin.getPlugin().getCVSWorkspaceSubscriber(), consultModels);
}
public static WorkspaceSubscriberContext createContext(ISynchronizationScopeManager manager, int type) {
CVSWorkspaceSubscriber subscriber = CVSProviderPlugin.getPlugin().getCVSWorkspaceSubscriber();
WorkspaceSubscriberContext mergeContext = new WorkspaceSubscriberContext(subscriber, manager, type);
mergeContext.initialize();
return mergeContext;
}
@Override
protected DiffFilter getDiffFilter() {
final DiffFilter contentFilter = createContentFilter();
final DiffFilter regexFilter = createRegexFilter();
if (contentFilter != null && regexFilter != null) {
return new DiffFilter() {
@Override
public boolean select(IDiff diff, IProgressMonitor monitor) {
return !contentFilter.select(diff, monitor)
&& !regexFilter.select(diff, monitor);
}
};
} else if (contentFilter != null) {
return new DiffFilter() {
@Override
public boolean select(IDiff diff, IProgressMonitor monitor) {
return !contentFilter.select(diff, monitor);
}
};
} else if (regexFilter != null) {
return new DiffFilter() {
@Override
public boolean select(IDiff diff, IProgressMonitor monitor) {
return !regexFilter.select(diff, monitor);
}
};
}
return null;
}
protected WorkspaceSubscriberContext(CVSWorkspaceSubscriber subscriber, ISynchronizationScopeManager manager, int type) {
super(subscriber, manager);
this.type = type;
((IEclipsePreferences) CVSUIPlugin.getPlugin().getInstancePreferences().node("")).addPreferenceChangeListener(this); //$NON-NLS-1$
}
@Override
public void dispose() {
super.dispose();
((IEclipsePreferences) CVSUIPlugin.getPlugin().getInstancePreferences().node("")).removePreferenceChangeListener(this); //$NON-NLS-1$
}
private boolean isConsiderContents() {
return CVSUIPlugin.getPlugin().getPreferenceStore().getBoolean(ICVSUIConstants.PREF_CONSIDER_CONTENTS);
}
private DiffFilter createContentFilter() {
if (isConsiderContents()) {
// Return a filter that selects any diffs whose contents are not equal
return new ContentComparisonDiffFilter(false);
}
return null;
}
private DiffFilter createRegexFilter() {
if (isConsiderContents()) {
String pattern = CVSUIPlugin.getPlugin().getPreferenceStore().getString(
ICVSUIConstants.PREF_SYNCVIEW_REGEX_FILTER_PATTERN);
if (pattern != null && !pattern.equals("")) { //$NON-NLS-1$
return new RegexDiffFilter(pattern);
}
}
return null;
}
@Override
public void preferenceChange(PreferenceChangeEvent event) {
if (event.getKey().equals(ICVSUIConstants.PREF_CONSIDER_CONTENTS) || event.getKey().equals(ICVSUIConstants.PREF_SYNCVIEW_REGEX_FILTER_PATTERN)) {
SubscriberDiffTreeEventHandler handler = getHandler();
if (handler != null) {
handler.setFilter(getDiffFilter());
handler.reset();
}
}
}
@Override
public void markAsMerged(IDiff[] nodes, boolean inSyncHint, IProgressMonitor monitor) throws CoreException {
if (getType() == TWO_WAY) {
// For, TWO_WAY merges (i.e. replace) should not adjust sync info
// but should remove the node from the tree so that other models do
// not modify the file
DiffTree tree = ((DiffTree)getDiffTree());
try {
tree.beginInput();
for (int i = 0; i < nodes.length; i++) {
IDiff diff = nodes[i];
tree.remove(diff.getPath());
}
} finally {
tree.endInput(monitor);
}
} else {
super.markAsMerged(nodes, inSyncHint, monitor);
}
}
@Override
public void markAsMerged(final IDiff diff, final boolean inSyncHint, IProgressMonitor monitor) throws CoreException {
run(monitor1 -> {
// Get the latest sync info for the file (i.e. not what is in the set).
// We do this because the client may have modified the file since the
// set was populated.
IResource resource = getDiffTree().getResource(diff);
if (resource.getType() != IResource.FILE) {
if (diff instanceof IThreeWayDiff) {
IThreeWayDiff twd = (IThreeWayDiff) diff;
if (resource.getType() == IResource.FOLDER
&& twd.getKind() == IDiff.ADD
&& twd.getDirection() == IThreeWayDiff.INCOMING
&& resource.exists()) {
// The folder was created by merge
SyncInfo info1 = getSyncInfo(resource);
if (info1 instanceof CVSSyncInfo) {
CVSSyncInfo cvsInfo1 = (CVSSyncInfo) info1;
cvsInfo1.makeInSync();
}
}
}
return;
}
if (getType() == TWO_WAY) {
// For, TWO_WAY merges (i.e. replace) should not adjust sync info
// but should remove the node from the tree so that other models do
// not modify the file
((DiffTree)getDiffTree()).remove(diff.getPath());
} else {
SyncInfo info2 = getSyncInfo(resource);
ensureRemotesMatch(resource, diff, info2);
if (info2 instanceof CVSSyncInfo) {
CVSSyncInfo cvsInfo2 = (CVSSyncInfo) info2;
monitor1.beginTask(null, 50 + (inSyncHint ? 100 : 0));
cvsInfo2.makeOutgoing(Policy.subMonitorFor(monitor1, 50));
if (inSyncHint) {
// Compare the contents of the file with the remote
// and make the file in-sync if they match
ContentComparisonSyncInfoFilter comparator = new SyncInfoFilter.ContentComparisonSyncInfoFilter(false);
if (resource.getType() == IResource.FILE && info2.getRemote() != null) {
if (comparator.compareContents((IFile)resource, info2.getRemote(), Policy.subMonitorFor(monitor1, 100))) {
ICVSFile cvsFile = CVSWorkspaceRoot.getCVSFileFor((IFile)resource);
cvsFile.checkedIn(null, false /* not a commit */);
}
}
}
monitor1.done();
}
}
}, getMergeRule(diff), IResource.NONE, monitor);
}
@Override
protected void makeInSync(final IDiff diff, IProgressMonitor monitor) throws CoreException {
run(monitor1 -> {
// Get the latest sync info for the file (i.e. not what is in the set).
// We do this because the client may have modified the file since the
// set was populated.
IResource resource = getDiffTree().getResource(diff);
if (resource.getType() != IResource.FILE)
return;
SyncInfo info = getSyncInfo(resource);
ensureRemotesMatch(resource, diff, info);
IResourceVariant remote = info.getRemote();
RemoteFile file = (RemoteFile)remote;
if (file != null)
remote = file.getCachedHandle();
if (info instanceof CVSSyncInfo) {
CVSSyncInfo cvsInfo = (CVSSyncInfo) info;
cvsInfo.makeOutgoing(monitor1);
if (resource.getType() == IResource.FILE && info.getRemote() != null) {
ICVSFile cvsFile = CVSWorkspaceRoot.getCVSFileFor((IFile)resource);
if (remote != null && remote instanceof RemoteFile){
cvsFile.setExecutable(((RemoteFile)remote).isExecutable());
cvsFile.setTimeStamp(((RemoteFile) remote).getTimeStamp());
cvsFile.setReadOnly(getReadOnly(cvsFile));
}
cvsFile.checkedIn(null , false /* not a commit */);
}
}
}, getMergeRule(diff), IResource.NONE, monitor);
}
protected boolean getReadOnly(ICVSFile cvsFile) {
IResource resource = cvsFile.getIResource();
RepositoryProvider provider = RepositoryProvider.getProvider(resource.getProject());
if (provider instanceof CVSTeamProvider) {
CVSTeamProvider ctp = (CVSTeamProvider) provider;
try {
return ctp.isWatchEditEnabled();
} catch (CVSException e) {
CVSUIPlugin.log(e);
}
}
return false;
}
protected void ensureRemotesMatch(IResource resource, IDiff node, SyncInfo info) throws CVSException {
IResourceVariant variant = info.getRemote();
IFileRevision remote = getRemote(node);
if (variant != null && remote != null && remote instanceof IFileRevision) {
String ci1 = variant.getContentIdentifier();
String ci2 = remote.getContentIdentifier();
if (!ci1.equals(ci2)) {
throw new CVSException(NLS.bind(CVSUIMessages.WorkspaceSubscriberContext_0, resource.getFullPath().toString()));
}
}
}
private IFileRevision getRemote(IDiff node) {
if (node == null) return null;
if (node instanceof IThreeWayDiff) {
IThreeWayDiff twd = (IThreeWayDiff) node;
return getRemote(twd.getRemoteChange());
}
if (node instanceof IResourceDiff) {
IResourceDiff rd = (IResourceDiff) node;
return rd.getAfterState();
}
return null;
}
@Override
public IStatus merge(IDiff delta, boolean force, IProgressMonitor monitor) throws CoreException {
if (getMergeType() == ISynchronizationContext.TWO_WAY) {
force = true;
}
// First, verify that the provided delta matches the current state
// i.e. it is possible that a concurrent change has occurred
IThreeWayDiff currentDiff = (IThreeWayDiff)getSubscriber().getDiff(getDiffTree().getResource(delta));
if (currentDiff == null
|| currentDiff.getKind() == IDiff.NO_CHANGE
|| (currentDiff.getDirection() == IThreeWayDiff.OUTGOING && !force)) {
// Seems like this one was already merged so return OK
return Status.OK_STATUS;
}
if (!equals(currentDiff, (IThreeWayDiff)delta)) {
throw new CVSException(NLS.bind(CVSUIMessages.CVSMergeContext_1, delta.getPath()));
}
try {
monitor.beginTask(null, 100);
IStatus status = super.merge(delta, force, Policy.subMonitorFor(monitor, 99));
if (status.isOK()) {
IResource resource = getDiffTree().getResource(delta);
if (resource.getType() == IResource.FILE && !resource.exists()) {
ICVSResource localResource = CVSWorkspaceRoot.getCVSResourceFor(resource);
localResource.unmanage(Policy.subMonitorFor(monitor, 1));
}
pruneEmptyParents(new IDiff[] { delta });
}
return status;
} finally {
monitor.done();
}
}
private boolean equals(IThreeWayDiff currentDiff, IThreeWayDiff otherDiff) {
return currentDiff.getKind() == otherDiff.getKind()
&& currentDiff.getDirection() == otherDiff.getDirection();
}
private void pruneEmptyParents(IDiff[] deltas) throws CVSException {
// TODO: A more explicit tie in to the pruning mechanism would be preferable.
// i.e. I don't like referencing the option and visitor directly
if (!CVSProviderPlugin.getPlugin().getPruneEmptyDirectories()) return;
ICVSResource[] cvsResources = new ICVSResource[deltas.length];
for (int i = 0; i < cvsResources.length; i++) {
cvsResources[i] = CVSWorkspaceRoot.getCVSResourceFor(getDiffTree().getResource(deltas[i]));
}
new PruneFolderVisitor().visit(
CVSWorkspaceRoot.getCVSFolderFor(ResourcesPlugin.getWorkspace().getRoot()),
cvsResources);
}
@Override
public int getMergeType() {
return type;
}
@Override
public void refresh(final ResourceTraversal[] traversals, int flags, IProgressMonitor monitor) throws CoreException {
SubscriberDiffTreeEventHandler handler = getHandler();
if (handler != null) {
GroupProgressMonitor group = getGroup(monitor);
if (group != null)
handler.setProgressGroupHint(group.getGroup(), group.getTicks());
handler.initializeIfNeeded();
((CVSWorkspaceSubscriber)getSubscriber()).refreshWithContentFetch(traversals, monitor);
runInBackground(monitor1 -> cacheContents(traversals, getDiffTree(), true, monitor1));
} else {
super.refresh(traversals, flags, monitor);
runInBackground(monitor1 -> cacheContents(traversals, getDiffTree(), false, monitor1));
}
}
private GroupProgressMonitor getGroup(IProgressMonitor monitor) {
if (monitor instanceof GroupProgressMonitor) {
return (GroupProgressMonitor) monitor;
}
if (monitor instanceof ProgressMonitorWrapper) {
ProgressMonitorWrapper wrapper = (ProgressMonitorWrapper) monitor;
return getGroup(wrapper.getWrappedProgressMonitor());
}
return null;
}
protected void cacheContents(final ResourceTraversal[] traversals, IResourceDiffTree tree, boolean baseOnly, IProgressMonitor monitor) throws CVSException {
// cache the base and remote contents
// TODO: Refreshing and caching now takes 3 round trips.
// OPTIMIZE: remote state and contents could be obtained in 1
// OPTIMIZE: Based could be avoided if we always cached base locally
ResourceMapping[] mappings = new ResourceMapping[] { new ResourceMapping() {
@Override
public Object getModelObject() {
return WorkspaceSubscriberContext.this;
}
@Override
public IProject[] getProjects() {
return ResourcesPlugin.getWorkspace().getRoot().getProjects();
}
@Override
public ResourceTraversal[] getTraversals(ResourceMappingContext context, IProgressMonitor monitor) throws CoreException {
return traversals;
}
@Override
public boolean contains(ResourceMapping mapping) {
return false;
}
@Override
public String getModelProviderId() {
return ModelProvider.RESOURCE_MODEL_PROVIDER_ID;
}
}};
try {
monitor.beginTask(null, 50);
new CacheBaseContentsOperation(null, mappings, tree, true).run(Policy.subMonitorFor(monitor, 25));
if (!baseOnly) {
new CacheRemoteContentsOperation(null, mappings, tree).run(Policy.subMonitorFor(monitor, 25));
}
} catch (InvocationTargetException e) {
throw CVSException.wrapException(e);
} catch (InterruptedException e) {
// Ignore
} finally {
monitor.done();
}
}
@Override
public IStatus merge(IDiff[] deltas, boolean force, IProgressMonitor monitor) throws CoreException {
try {
if (deltas.length == 0)
return Status.OK_STATUS;
String taskName = getMergeTaskName(deltas, force);
monitor.beginTask(taskName, 100);
monitor.setTaskName(taskName);
cacheContents(getTraversals(deltas), getDiffTree(deltas), false, Policy.subMonitorFor(monitor, 30));
return super.merge(deltas, force, Policy.subMonitorFor(monitor, 70));
} finally {
monitor.done();
}
}
private String getMergeTaskName(IDiff[] deltas, boolean force) {
if (force) {
if (deltas.length == 1) {
return NLS.bind(CVSUIMessages.WorkspaceSubscriberContext_1, getDiffTree().getResource(deltas[0]).getFullPath());
}
return NLS.bind(CVSUIMessages.WorkspaceSubscriberContext_2, Integer.valueOf(deltas.length));
}
if (deltas.length == 1) {
return NLS.bind(CVSUIMessages.WorkspaceSubscriberContext_3, getDiffTree().getResource(deltas[0]).getFullPath());
}
return NLS.bind(CVSUIMessages.WorkspaceSubscriberContext_4, Integer.valueOf(deltas.length));
}
private ResourceTraversal[] getTraversals(IDiff[] deltas) {
List<IResource> result = new ArrayList<>();
for (int i = 0; i < deltas.length; i++) {
IDiff diff = deltas[i];
IResource resource = ResourceDiffTree.getResourceFor(diff);
if (resource != null) {
result.add(resource);
}
}
return new ResourceTraversal[] {
new ResourceTraversal(result.toArray(new IResource[result.size()]), IResource.DEPTH_ZERO, IResource.NONE)
};
}
private IResourceDiffTree getDiffTree(IDiff[] deltas) {
ResourceDiffTree tree = new ResourceDiffTree();
for (int i = 0; i < deltas.length; i++) {
IDiff diff = deltas[i];
tree.add(diff);
}
return tree;
}
@Override
protected void performReplace(IDiff diff, IProgressMonitor monitor) throws CoreException {
IResource resource = ResourceDiffTree.getResourceFor(diff);
if (resource.getType() == IResource.FILE){
IFile file = (IFile) resource;
ICVSFile mFile = CVSWorkspaceRoot.getCVSFileFor(file);
try {
// The file may have been set as read-only by a previous checkout/update
if (mFile.isReadOnly()) mFile.setReadOnly(false);
} catch (CVSException e) {
// Just log and keep going
CVSProviderPlugin.log(e);
}
}
super.performReplace(diff, monitor);
}
}