package org.eclipse.team.core.mapping.provider;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceRuleFactory;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.MultiRule;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.diff.IDiff;
import org.eclipse.team.core.diff.IThreeWayDiff;
import org.eclipse.team.core.history.IFileRevision;
import org.eclipse.team.core.mapping.DelegatingStorageMerger;
import org.eclipse.team.core.mapping.IMergeContext;
import org.eclipse.team.core.mapping.IMergeStatus;
import org.eclipse.team.core.mapping.IResourceDiff;
import org.eclipse.team.core.mapping.IResourceDiffTree;
import org.eclipse.team.core.mapping.IResourceMappingMerger;
import org.eclipse.team.core.mapping.IStorageMerger;
import org.eclipse.team.core.mapping.ISynchronizationScopeManager;
import org.eclipse.team.internal.core.Messages;
import org.eclipse.team.internal.core.Policy;
import org.eclipse.team.internal.core.TeamPlugin;
import org.eclipse.team.internal.core.mapping.SyncInfoToDiffConverter;
public abstract class MergeContext extends SynchronizationContext implements IMergeContext {
protected MergeContext(ISynchronizationScopeManager manager, int type, IResourceDiffTree deltaTree) {
super(manager, type, deltaTree);
}
@Override
public void reject(final IDiff[] diffs, IProgressMonitor monitor) throws CoreException {
run(monitor1 -> {
for (IDiff node : diffs) {
reject(node, monitor1);
}
}, getMergeRule(diffs), IResource.NONE, monitor);
}
@Override
public void markAsMerged(final IDiff[] nodes, final boolean inSyncHint, IProgressMonitor monitor) throws CoreException {
run(monitor1 -> {
for (IDiff node : nodes) {
markAsMerged(node, inSyncHint, monitor1);
}
}, getMergeRule(nodes), IResource.NONE, monitor);
}
@Override
public IStatus merge(final IDiff[] deltas, final boolean force, IProgressMonitor monitor) throws CoreException {
final List<IFile> failedFiles = new ArrayList<>();
run(monitor1 -> {
try {
monitor1.beginTask(null, deltas.length * 100);
for (IDiff delta : deltas) {
IStatus s = merge(delta, force, Policy.subMonitorFor(monitor1, 100));
if (!s.isOK()) {
if (s.getCode() == IMergeStatus.CONFLICTS) {
failedFiles.addAll(Arrays.asList(((IMergeStatus)s).getConflictingFiles()));
} else {
throw new CoreException(s);
}
}
}
} finally {
monitor1.done();
}
}, getMergeRule(deltas), IWorkspace.AVOID_UPDATE, monitor);
if (failedFiles.isEmpty()) {
return Status.OK_STATUS;
} else {
return new MergeStatus(TeamPlugin.ID, Messages.MergeContext_0, failedFiles.toArray(new IFile[failedFiles.size()]));
}
}
@Override
public IStatus merge(IDiff diff, boolean ignoreLocalChanges, IProgressMonitor monitor) throws CoreException {
Policy.checkCanceled(monitor);
IResource resource = getDiffTree().getResource(diff);
if (resource.getType() != IResource.FILE) {
if (diff instanceof IThreeWayDiff) {
IThreeWayDiff twd = (IThreeWayDiff) diff;
if ((ignoreLocalChanges || getMergeType() == TWO_WAY)
&& resource.getType() == IResource.FOLDER
&& twd.getKind() == IDiff.ADD
&& twd.getDirection() == IThreeWayDiff.OUTGOING
&& ((IFolder)resource).members().length == 0) {
((IFolder)resource).delete(false, monitor);
} else if (resource.getType() == IResource.FOLDER
&& !resource.exists()
&& twd.getKind() == IDiff.ADD
&& twd.getDirection() == IThreeWayDiff.INCOMING) {
ensureParentsExist(resource, monitor);
((IFolder)resource).create(false, true, monitor);
makeInSync(diff, monitor);
}
}
return Status.OK_STATUS;
}
if (diff instanceof IThreeWayDiff && !ignoreLocalChanges && getMergeType() == THREE_WAY) {
IThreeWayDiff twDelta = (IThreeWayDiff) diff;
int direction = twDelta.getDirection();
if (direction == IThreeWayDiff.OUTGOING) {
return Status.OK_STATUS;
}
if (direction == IThreeWayDiff.INCOMING) {
performReplace(diff, monitor);
return Status.OK_STATUS;
}
int type = twDelta.getKind();
if (type == IDiff.REMOVE) {
makeInSync(diff, monitor);
return Status.OK_STATUS;
}
IResourceDiff remoteChange = (IResourceDiff)twDelta.getRemoteChange();
IFileRevision remote = null;
if (remoteChange != null) {
remote = remoteChange.getAfterState();
}
if (remote == null || !getLocalFile(diff).exists()) {
return new MergeStatus(TeamPlugin.ID, NLS.bind(Messages.MergeContext_1, new String[] { diff.getPath().toString() }), new IFile[] { getLocalFile(diff) });
}
return performThreeWayMerge(twDelta, monitor);
} else {
performReplace(diff, monitor);
return Status.OK_STATUS;
}
}
protected IStatus performThreeWayMerge(final IThreeWayDiff diff, IProgressMonitor monitor) throws CoreException {
final IStatus[] result = new IStatus[] { Status.OK_STATUS };
run(monitor1 -> {
monitor1.beginTask(null, 100);
IResourceDiff localDiff = (IResourceDiff)diff.getLocalChange();
IResourceDiff remoteDiff = (IResourceDiff)diff.getRemoteChange();
IStorageMerger merger = getAdapter(IStorageMerger.class);
if (merger == null)
merger = DelegatingStorageMerger.getInstance();
IFile file = (IFile)localDiff.getResource();
monitor1.subTask(NLS.bind(Messages.MergeContext_5, file.getFullPath().toString()));
String osEncoding = file.getCharset();
IFileRevision ancestorState = localDiff.getBeforeState();
IFileRevision remoteState = remoteDiff.getAfterState();
IStorage ancestorStorage;
if (ancestorState != null)
ancestorStorage = ancestorState.getStorage(Policy.subMonitorFor(monitor1, 30));
else
ancestorStorage = null;
IStorage remoteStorage = remoteState.getStorage(Policy.subMonitorFor(monitor1, 30));
OutputStream os = getTempOutputStream(file);
try {
IStatus status = merger.merge(os, osEncoding, ancestorStorage, file, remoteStorage, Policy.subMonitorFor(monitor1, 30));
if (status.isOK()) {
file.setContents(getTempInputStream(file, os), false, true, Policy.subMonitorFor(monitor1, 5));
markAsMerged(diff, false, Policy.subMonitorFor(monitor1, 5));
} else {
status = new MergeStatus(status.getPlugin(), status.getMessage(), new IFile[]{file});
}
result[0] = status;
} finally {
disposeTempOutputStream(file, os);
}
monitor1.done();
}, getMergeRule(diff), IWorkspace.AVOID_UPDATE, monitor);
return result[0];
}
private void disposeTempOutputStream(IFile file, OutputStream output) {
if (output instanceof ByteArrayOutputStream)
return;
try {
if (output != null)
output.close();
} catch (IOException e) {
}
File tmpFile = getTempFile(file);
if (tmpFile.exists())
tmpFile.delete();
}
private OutputStream getTempOutputStream(IFile file) throws CoreException {
File tmpFile = getTempFile(file);
if (tmpFile.exists())
tmpFile.delete();
File parent = tmpFile.getParentFile();
if (!parent.exists())
parent.mkdirs();
try {
return new BufferedOutputStream(new FileOutputStream(tmpFile));
} catch (FileNotFoundException e) {
TeamPlugin.log(IStatus.ERROR, NLS.bind("Could not open temporary file {0} for writing: {1}", new String[] { tmpFile.getAbsolutePath(), e.getMessage() }), e);
return new ByteArrayOutputStream();
}
}
private InputStream getTempInputStream(IFile file, OutputStream output) throws CoreException {
if (output instanceof ByteArrayOutputStream) {
ByteArrayOutputStream baos = (ByteArrayOutputStream) output;
return new ByteArrayInputStream(baos.toByteArray());
}
try {
if (output != null)
output.close();
} catch (IOException e) {
}
File tmpFile = getTempFile(file);
try {
return new BufferedInputStream(new FileInputStream(tmpFile));
} catch (FileNotFoundException e) {
throw new CoreException(new Status(IStatus.ERROR, TeamPlugin.ID, IMergeStatus.INTERNAL_ERROR, NLS.bind(Messages.MergeContext_4, new String[] { tmpFile.getAbsolutePath(), e.getMessage() }), e));
}
}
private File getTempFile(IFile file) {
return TeamPlugin.getPlugin().getStateLocation().append(".tmp").append(file.getName() + ".tmp").toFile();
}
private IFile getLocalFile(IDiff delta) {
return ResourcesPlugin.getWorkspace().getRoot().getFile(delta.getPath());
}
protected void performReplace(final IDiff diff, IProgressMonitor monitor) throws CoreException {
IResourceDiff d;
IFile file = getLocalFile(diff);
IFileRevision remote = null;
if (diff instanceof IResourceDiff) {
d = (IResourceDiff) diff;
remote = d.getAfterState();
} else {
d = (IResourceDiff)((IThreeWayDiff)diff).getRemoteChange();
if (d != null)
remote = d.getAfterState();
}
if (d == null) {
d = (IResourceDiff)((IThreeWayDiff)diff).getLocalChange();
if (d != null)
remote = d.getBeforeState();
}
if (d != null) {
performReplace(diff, file, remote, monitor);
}
}
protected abstract void makeInSync(IDiff diff, IProgressMonitor monitor) throws CoreException;
private void performReplace(final IDiff diff, final IFile file, final IFileRevision remote, IProgressMonitor monitor) throws CoreException {
run(monitor1 -> {
try {
monitor1.beginTask(null, 100);
monitor1.subTask(NLS.bind(Messages.MergeContext_6, file.getFullPath().toString()));
if ((remote == null || !remote.exists()) && file.exists()) {
file.delete(false, true, Policy.subMonitorFor(monitor1, 95));
} else if (remote != null) {
ensureParentsExist(file, monitor1);
InputStream stream = remote.getStorage(monitor1).getContents();
stream = new BufferedInputStream(stream);
try {
if (file.exists()) {
file.setContents(stream, false, true, Policy.subMonitorFor(monitor1, 95));
} else {
file.create(stream, false, Policy.subMonitorFor(monitor1, 95));
}
} finally {
try {
stream.close();
} catch (IOException e) {
}
}
}
makeInSync(diff, Policy.subMonitorFor(monitor1, 5));
} finally {
monitor1.done();
}
}, getMergeRule(diff), IWorkspace.AVOID_UPDATE, monitor);
}
protected void ensureParentsExist(IResource resource, IProgressMonitor monitor) throws CoreException {
IContainer parent = resource.getParent();
if (parent.getType() != IResource.FOLDER) {
return;
}
if (!parent.exists()) {
ensureParentsExist(parent, monitor);
((IFolder)parent).create(false, true, monitor);
}
}
@Override
public void run(IWorkspaceRunnable runnable, ISchedulingRule rule, int flags, IProgressMonitor monitor) throws CoreException {
ResourcesPlugin.getWorkspace().run(runnable, rule, flags, monitor);
}
@Override
public ISchedulingRule getMergeRule(IDiff diff) {
IResource resource = getDiffTree().getResource(diff);
IResourceRuleFactory ruleFactory = ResourcesPlugin.getWorkspace().getRuleFactory();
ISchedulingRule rule;
if (!resource.exists()) {
IContainer parent = resource.getParent();
while (!parent.exists()) {
resource = parent;
parent = parent.getParent();
}
rule = ruleFactory.createRule(resource);
} else if (SyncInfoToDiffConverter.getRemote(diff) == null){
rule = ruleFactory.deleteRule(resource);
} else {
rule = ruleFactory.modifyRule(resource);
}
return rule;
}
@Override
public ISchedulingRule getMergeRule(IDiff[] deltas) {
ISchedulingRule result = null;
for (IDiff node : deltas) {
ISchedulingRule rule = getMergeRule(node);
if (result == null) {
result = rule;
} else {
result = MultiRule.combine(result, rule);
}
}
return result;
}
@Override
public int getMergeType() {
return getType();
}
@Override
@SuppressWarnings("unchecked")
public <T> T getAdapter(Class<T> adapter) {
if (adapter == IStorageMerger.class) {
return (T) DelegatingStorageMerger.getInstance();
}
return super.getAdapter(adapter);
}
}