Skip to main content
summaryrefslogtreecommitdiffstats
blob: c6c64eb964cfe1a95ef6669835827052ee77d788 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
/*******************************************************************************
 * Copyright (c) 2016 Obeo.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Obeo - initial API and implementation
 *******************************************************************************/
package org.eclipse.emf.compare.ide.ui.internal.logical;

import static org.eclipse.emf.compare.ConflictKind.PSEUDO;
import static org.eclipse.emf.compare.ConflictKind.REAL;
import static org.eclipse.emf.compare.DifferenceKind.DELETE;
import static org.eclipse.emf.compare.DifferenceSource.LEFT;
import static org.eclipse.emf.compare.DifferenceState.DISCARDED;
import static org.eclipse.emf.compare.DifferenceState.MERGED;
import static org.eclipse.emf.compare.merge.AbstractMerger.getMergerDelegate;
import static org.eclipse.emf.compare.utils.EMFComparePredicates.hasConflict;

import com.google.common.collect.Sets;
import com.google.common.io.ByteStreams;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedHashSet;
import java.util.Set;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.mapping.ResourceMapping;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.emf.common.util.BasicMonitor;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.Monitor;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.Conflict;
import org.eclipse.emf.compare.Diff;
import org.eclipse.emf.compare.DifferenceState;
import org.eclipse.emf.compare.EMFCompare;
import org.eclipse.emf.compare.EMFCompare.Builder;
import org.eclipse.emf.compare.ReferenceChange;
import org.eclipse.emf.compare.graph.IGraph;
import org.eclipse.emf.compare.graph.PruningIterator;
import org.eclipse.emf.compare.ide.IAdditiveResourceMappingMerger;
import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIMessages;
import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
import org.eclipse.emf.compare.ide.ui.logical.IModelMinimizer;
import org.eclipse.emf.compare.ide.ui.logical.SynchronizationModel;
import org.eclipse.emf.compare.ide.utils.ResourceUtil;
import org.eclipse.emf.compare.internal.merge.MergeDependenciesUtil;
import org.eclipse.emf.compare.merge.AdditiveMergeCriterion;
import org.eclipse.emf.compare.rcp.internal.extension.impl.EMFCompareBuilderConfigurator;
import org.eclipse.emf.compare.scope.IComparisonScope;
import org.eclipse.team.core.diff.IDiff;
import org.eclipse.team.core.mapping.IMergeContext;

public class AdditiveResourceMappingMerger extends EMFResourceMappingMerger implements IAdditiveResourceMappingMerger {

	public AdditiveResourceMappingMerger() {
		super();
	}

	@Override
	protected void mergeMapping(ResourceMapping mapping, IMergeContext mergeContext,
			Set<ResourceMapping> failingMappings, IProgressMonitor monitor) throws CoreException {
		final SubMonitor subMonitor = SubMonitor.convert(monitor, 10);
		// validateMappings() has made sure we only have EMFResourceMappings
		final SynchronizationModel syncModel = ((EMFResourceMapping)mapping).getLatestModel();
		// we may have non-existing storages in the left traversal, so let's get rid of them
		removeNonExistingStorages(syncModel.getLeftTraversal());
		// get the involved resources before we run the minimizer
		final Set<IResource> resources = Sets.newLinkedHashSet(syncModel.getResources());

		final IModelMinimizer minimizer = new IdenticalResourceMinimizer();
		minimizer.minimize(syncModel, subMonitor.newChild(1)); // 10%
		final IComparisonScope scope = ComparisonScopeBuilder.create(syncModel, subMonitor.newChild(3)); // 40%

		final Builder builder = EMFCompare.builder();
		EMFCompareBuilderConfigurator.createDefault().configure(builder);

		final Comparison comparison = builder.build().compare(scope,
				BasicMonitor.toMonitor(SubMonitor.convert(subMonitor.newChild(1), 10))); // 50%

		final ResourceAdditionAndDeletionTracker resourceTracker = new ResourceAdditionAndDeletionTracker();
		final Set<URI> conflictingURIs = performPreMerge(comparison, subMonitor.newChild(3)); // 80%
		save(scope.getLeft(), syncModel.getLeftTraversal(), syncModel.getRightTraversal(),
				syncModel.getOriginTraversal());
		if (!conflictingURIs.isEmpty()) {
			failingMappings.add(mapping);
			markResourcesAsMerged(mergeContext, resources, conflictingURIs, subMonitor.newChild(2)); // 100%
		} else {
			delegateMergeOfUnmergedResourcesAndMarkDiffsAsMerged(syncModel, mergeContext, resourceTracker,
					subMonitor.newChild(2)); // 100%
		}

		scope.getLeft().eAdapters().remove(resourceTracker);
		subMonitor.setWorkRemaining(0);
	}

	private Set<URI> performPreMerge(Comparison comparison, SubMonitor subMonitor) {
		final IGraph<Diff> differencesGraph = MergeDependenciesUtil.mapDifferences(comparison,
				MERGER_REGISTRY, true, null);
		final PruningIterator<Diff> iterator = differencesGraph.breadthFirstIterator();
		final Monitor emfMonitor = BasicMonitor.toMonitor(subMonitor);

		final Set<URI> conflictingURIs = new LinkedHashSet<URI>();

		while (iterator.hasNext()) {
			final Diff next = iterator.next();
			doMergeForDiff(next, differencesGraph, iterator, emfMonitor, conflictingURIs);
		}

		return conflictingURIs;
	}

	private void doMergeForDiff(final Diff diff, final IGraph<Diff> differencesGraph,
			final PruningIterator<Diff> iterator, final Monitor emfMonitor, final Set<URI> conflictingURIs) {
		if (diff.getState() != DifferenceState.UNRESOLVED) {
			return;
		}
		if (hasConflict(REAL).apply(diff)) {
			if (isInAdditiveConflict(diff)) {
				// The diff is part of conflict that can be considered as an additive conflict.
				if (diff.getSource() == LEFT) {
					if (isRequiredByDeletion(diff)) {
						// Deletion from left side must be overriden by right changes
						getMergerDelegate(diff, MERGER_REGISTRY, AdditiveMergeCriterion.INSTANCE)
								.copyRightToLeft(diff, emfMonitor);
					} else {
						// other left changes have to be kept. Mark them as merged
						diff.setState(MERGED);
					}
				} else {
					if (isRequiredByDeletion(diff)) {
						// Deletion from right side must not be merged. Mark them as merged
						diff.setState(DISCARDED);
					} else {
						// Copy all other changes to left side
						getMergerDelegate(diff, MERGER_REGISTRY, AdditiveMergeCriterion.INSTANCE)
								.copyRightToLeft(diff, emfMonitor);
					}
				}
			} else {
				// The diff is part of a real conflict. Mark the resource as conflicting.
				iterator.prune();
				conflictingURIs
						.addAll(collectConflictingResources(differencesGraph.depthFirstIterator(diff)));
			}

		} else if (isPseudoConflicting(diff)) {
			EList<Diff> conflictingDiffs = diff.getConflict().getDifferences();
			for (Diff conflictingDiff : conflictingDiffs) {
				conflictingDiff.setState(MERGED);
			}
		} else if (diff.getSource() == LEFT) {
			if (isRequiredByDeletion(diff)) {
				getMergerDelegate(diff, MERGER_REGISTRY, AdditiveMergeCriterion.INSTANCE)
						.copyRightToLeft(diff, emfMonitor);
			} else {
				diff.setState(MERGED);
			}
		} else {
			// Diff from RIGHT
			if (isRequiredByDeletion(diff)) {
				diff.setState(DISCARDED);
			} else {
				getMergerDelegate(diff, MERGER_REGISTRY, AdditiveMergeCriterion.INSTANCE)
						.copyRightToLeft(diff, emfMonitor);
			}
		}
	}

	@Override
	protected void delegateMergeOfUnmergedResourcesAndMarkDiffsAsMerged(SynchronizationModel syncModel,
			IMergeContext mergeContext, ResourceAdditionAndDeletionTracker resourceTracker,
			SubMonitor subMonitor) throws CoreException {
		// mark already deleted files as merged
		for (IFile deletedFile : resourceTracker.getDeletedIFiles()) {
			final IDiff diff = mergeContext.getDiffTree().getDiff(deletedFile);
			markAsMerged(diff, mergeContext, subMonitor);
		}

		// for all left storages, delegate the merge of a deletion that has not been performed yet and mark
		// all already performed diffs as merged
		for (IStorage storage : syncModel.getLeftTraversal().getStorages()) {
			final IPath fullPath = ResourceUtil.getFixedPath(storage);
			if (fullPath == null) {
				EMFCompareIDEUIPlugin.getDefault().getLog().log(new Status(IStatus.WARNING,
						EMFCompareIDEUIPlugin.PLUGIN_ID,
						EMFCompareIDEUIMessages.getString("EMFResourceMappingMerger.mergeIncomplete"))); //$NON-NLS-1$
			} else {
				final IDiff diff = mergeContext.getDiffTree().getDiff(fullPath);
				if (diff != null) {
					markAsMerged(diff, mergeContext, subMonitor.newChild(1));
				}
			}
		}

		// delegate all additions from the right storages that have not been performed yet
		// or, if they have been merged, mark the diff as merged
		for (IStorage rightStorage : syncModel.getRightTraversal().getStorages()) {
			final IPath fullPath = ResourceUtil.getFixedPath(rightStorage);
			if (fullPath != null) {
				final IDiff diff = mergeContext.getDiffTree().getDiff(fullPath);
				if (diff != null && IDiff.ADD == diff.getKind()) {
					if (!resourceTracker.containsAddedResource(fullPath)) {
						IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(fullPath);
						IProject project = file.getProject();
						if (project.isAccessible()) {
							merge(diff, mergeContext, subMonitor.newChild(1));
						} else {
							// The project that will contain the resource is not accessible.
							// We have to copy the file "manually" from the right side to the left side.
							try {
								InputStream inputStream = rightStorage.getContents();
								FileOutputStream outputStream = new FileOutputStream(
										ResourceUtil.getAbsolutePath(rightStorage).toFile());
								ByteStreams.copy(inputStream, outputStream);
								inputStream.close();
								outputStream.close();
							} catch (FileNotFoundException e) {
								EMFCompareIDEUIPlugin.getDefault().log(e);
								// TODO Should we throw the exception here to interrupt the merge ?
							} catch (IOException e) {
								EMFCompareIDEUIPlugin.getDefault().log(e);
								// TODO Should we throw the exception here to interrupt the merge ?
							}
						}
					} else {
						markAsMerged(diff, mergeContext, subMonitor.newChild(1));
					}
				}
			}
		}
	}

	/**
	 * Test if a diff or one of the diff that required this one are delete diffs.
	 * 
	 * @param diff
	 *            The given diff
	 * @return <code>true</code> if the diff or one of the diff that requires this one is a deletion
	 */
	private boolean isRequiredByDeletion(Diff diff) {
		if (diff.getKind() == DELETE) {
			return true;
		} else {
			EList<Diff> requiredBy = diff.getRequiredBy();
			for (Diff requiredDiff : requiredBy) {
				if (isRequiredByDeletion(requiredDiff)) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Test if a conflicting diff is part of a pseudo-conflict.
	 * 
	 * @param diff
	 *            The given diff
	 * @return <code>true</code> if the diff is part of a psudo conflict
	 */
	private boolean isPseudoConflicting(Diff diff) {
		Conflict conflict = diff.getConflict();
		if (conflict != null && conflict.getKind() == PSEUDO) {
			return true;
		}
		return false;
	}

	/**
	 * Test a conflicting diff to determine if it is contained in a conflict that can be considered as an
	 * additive conflict.
	 * 
	 * @param diff
	 *            The given diff
	 * @return <code>true</code> if the diff is part of an additive conflict
	 */
	private boolean isInAdditiveConflict(Diff diff) {
		boolean isAdditiveConflict = false;
		EList<Diff> leftDifferences = diff.getConflict().getLeftDifferences();
		EList<Diff> rightDifferences = diff.getConflict().getRightDifferences();
		if (inAdditiveConflict(leftDifferences)) {
			isAdditiveConflict = true;
		} else if (inAdditiveConflict(rightDifferences)) {
			isAdditiveConflict = true;
		}

		return isAdditiveConflict;
	}

	/**
	 * Test an list of diff representing one side of a conflict to determine if we are in an additive conflict
	 * configuration.
	 * 
	 * @param diffs
	 *            A list of diffs from on side of a conflict
	 * @return <code>true</code> if there is a deletion of a containment reference
	 */
	private boolean inAdditiveConflict(EList<Diff> diffs) {
		boolean isCorrect = false;
		for (Diff diff : diffs) {
			if (diff instanceof ReferenceChange) {
				ReferenceChange rc = (ReferenceChange)diff;
				if (rc.getReference().isContainment()) {
					isCorrect = true;
					break;
				}
			}
		}
		return isCorrect;
	}

}

Back to the top