Skip to main content
summaryrefslogtreecommitdiffstats
blob: 0ca25b5c55b78d59b24f4e1e27e2940b95932c03 (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
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
/*******************************************************************************
 * Copyright (c) 2015, 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:
 *     Laurent Goubet <laurent.goubet@obeo.fr> - initial API and implementation
 *     Axel Richard <axel.richard@obeo.fr> - Update #registerHandledFiles()
 *******************************************************************************/
package org.eclipse.emf.compare.egit.internal.merge;

//CHECKSTYLE:OFF
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

import org.apache.log4j.Logger;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.mapping.RemoteResourceMappingContext;
import org.eclipse.core.resources.mapping.ResourceMapping;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.egit.core.Activator;
import org.eclipse.egit.core.internal.job.RuleUtil;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.emf.compare.egit.internal.storage.AbstractGitResourceVariant;
import org.eclipse.emf.compare.egit.internal.storage.TreeParserResourceVariant;
import org.eclipse.emf.compare.egit.internal.wrapper.JGitProgressMonitorWrapper;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.dircache.DirCacheBuildIterator;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.merge.MergeResult;
import org.eclipse.jgit.merge.RecursiveMerger;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.diff.IDiff;
import org.eclipse.team.core.mapping.IMergeContext;
import org.eclipse.team.core.mapping.IResourceMappingMerger;
import org.eclipse.team.core.mapping.ISynchronizationScopeManager;
import org.eclipse.team.core.subscribers.Subscriber;
import org.eclipse.team.core.subscribers.SubscriberMergeContext;
import org.eclipse.team.core.subscribers.SubscriberResourceMappingContext;
import org.eclipse.team.core.subscribers.SubscriberScopeManager;
import org.eclipse.team.core.variants.IResourceVariant;

/**
 * This extends the recursive merger in order to take into account specific mergers provided by the Team
 * {@link org.eclipse.core.resources.mapping.ModelProvider model providers}.
 * <p>
 * The Recursive Merger handles files one-by-one, calling file-specific merge drivers for each. On the
 * opposite, this strategy can handle bigger sets of files at once, delegating the merge to the files' model.
 * As such, file-specific merge drivers may not be called from this strategy if that file is part of a larger
 * model.
 * </p>
 * <p>
 * Any file that is <b>not</b> part of a model, which model cannot be determined, or which model does not
 * specify a custom merger, will be handled as it would by the RecursiveMerger.
 * </p>
 * 
 * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
 * @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
 */
@SuppressWarnings("restriction")
public class RecursiveModelMerger extends RecursiveMerger {

	private static final Logger LOGGER = Logger.getLogger(RecursiveModelMerger.class);

	/**
	 * This will be populated during the course of the RecursiveMappingMergers' executions. These files have
	 * been cleanly merged and we should thus make sure the DirCacheBuilder takes their latest working
	 * directory version before committing.
	 */
	private final Set<String> makeInSync = new LinkedHashSet<String>();

	/**
	 * keeps track of the files we've already merged. Since we iterate one file at a time but may merge
	 * multiple files at once when they are part of the same model, this will help us avoid merging the same
	 * file or model twice.
	 */
	private final Set<String> handledPaths = new HashSet<String>();

	/**
	 * Default recursive model merger.
	 *
	 * @param db
	 * @param inCore
	 */
	public RecursiveModelMerger(Repository db, boolean inCore) {
		super(db, inCore);
	}

	@Override
	protected boolean mergeTreeWalk(TreeWalk treeWalk, boolean ignoreConflicts) throws IOException {
		if (LOGGER.isInfoEnabled()) {
			LOGGER.info("STARTING Recursive model merge."); //$NON-NLS-1$
		}
		final TreeWalkResourceVariantTreeProvider variantTreeProvider = new TreeWalkResourceVariantTreeProvider(
				getRepository(), treeWalk, T_BASE, T_OURS, T_THEIRS);
		final GitResourceVariantTreeSubscriber subscriber = new GitResourceVariantTreeSubscriber(
				variantTreeProvider);
		final RemoteResourceMappingContext remoteMappingContext = new SubscriberResourceMappingContext(
				subscriber, true);

		try {
			refreshRoots(subscriber.roots());
		} catch (CoreException e) {
			// We cannot be sure that Team and/or the merger implementations
			// will properly handle unrefreshed files. Fall back to merging
			// without workspace awareness.
			Activator.logError(MergeText.RecursiveModelMerger_RefreshError, e);
			return super.mergeTreeWalk(treeWalk, ignoreConflicts);
		}

		monitor.beginTask(MergeText.RecursiveModelMerger_BuildLogicalModels, ProgressMonitor.UNKNOWN);
		// Eager lookup for the logical models to avoid issues in case we
		// iterate over a file that does not exist locally before the rest of
		// its logical model.
		final LogicalModels logicalModels = new LogicalModels();
		logicalModels.build(variantTreeProvider.getKnownResources(), remoteMappingContext);
		monitor.endTask();

		if (monitor.isCancelled()) {
			throw new OperationCanceledException();
		}

		// We are done with the setup. We can now iterate over the tree walk and
		// either delegate to the logical model's merger if any or fall back to
		// standard git merging. Basically, any file that is not a part of a
		// logical model that defines its own specific merger will be handled as
		// it would by the RecursiveMerger.
		while (treeWalk.next()) {
			final int modeBase = treeWalk.getRawMode(T_BASE);
			final int modeOurs = treeWalk.getRawMode(T_OURS);
			final int modeTheirs = treeWalk.getRawMode(T_THEIRS);
			if (modeBase == 0 && modeOurs == 0 && modeTheirs == 0) {
				// untracked
				continue;
			}
			final String path = treeWalk.getPathString();
			if (handledPaths.contains(path)) {
				// This one has been handled as a result of a previous model
				// merge. Simply make sure we use its latest content if it is
				// not in conflict.
				if (treeWalk.isSubtree() && enterSubtree) {
					treeWalk.enterSubtree();
				}
				if (!unmergedPaths.contains(path)) {
					registerMergedPath(path);
					if (LOGGER.isDebugEnabled()) {
						LOGGER.debug("Ignoring previously handled file: " + path); //$NON-NLS-1$
					}
				}
				continue;
			}

			final int nonZeroMode = modeBase != 0 ? modeBase : modeOurs != 0 ? modeOurs : modeTheirs;
			final IResource resource = variantTreeProvider.getResourceHandleForLocation(getRepository(), path,
					FileMode.fromBits(nonZeroMode) == FileMode.TREE);
			Set<IResource> logicalModel = logicalModels.getModel(resource);

			IResourceMappingMerger modelMerger = null;
			if (logicalModel != null) {
				try {
					// We need to refresh because new resources may have been added
					refreshRoots(subscriber.roots());
					modelMerger = getResourceMappingMerger(logicalModel);
				} catch (CoreException e) {
					Activator.logError(MergeText.RecursiveModelMerger_AdaptError, e);
					// ignore this model and fall back to default
					if (!fallBackToDefaultMerge(treeWalk, ignoreConflicts)) {
						cleanUp();
						if (LOGGER.isInfoEnabled()) {
							LOGGER.info(
									"FAILED - Recursive model merge, could not find appropriate merger and default merge failed."); //$NON-NLS-1$
						}
						return false;
					}
				}
			}
			if (modelMerger != null) {
				enterSubtree = true;

				boolean success = new ModelMerge(this, subscriber, remoteMappingContext, path, logicalModel,
						modelMerger).run(new JGitProgressMonitorWrapper(monitor));
				if (!success) {
					if (LOGGER.isInfoEnabled()) {
						LOGGER.info("FAILED - Recursive model merge."); //$NON-NLS-1$
					}
					return false;
				} else if (!unmergedPaths.contains(path)) {
					if (LOGGER.isDebugEnabled()) {
						LOGGER.debug("Merged model file: " + path); //$NON-NLS-1$
					}
					registerMergedPath(path);
				}
				if (treeWalk.isSubtree()) {
					enterSubtree = true;
				}
			} else if (!fallBackToDefaultMerge(treeWalk, ignoreConflicts)) {
				cleanUp();
				if (LOGGER.isInfoEnabled()) {
					LOGGER.info("FAILED - Recursive model merge, default merge failed."); //$NON-NLS-1$
				}
				return false;
			}
			if (treeWalk.isSubtree() && enterSubtree) {
				treeWalk.enterSubtree();
			}
		}
		if (!makeInSync.isEmpty()) {
			indexModelMergedFiles();
		}
		if (LOGGER.isInfoEnabled()) {
			LOGGER.info("SUCCESS - Recursive model merge."); //$NON-NLS-1$
		}
		return true;
	}

	protected IResourceMappingMerger getResourceMappingMerger(Set<IResource> logicalModel)
			throws CoreException {
		return LogicalModels.findAdapter(logicalModel, IResourceMappingMerger.class);
	}

	private boolean fallBackToDefaultMerge(TreeWalk treeWalk, boolean ignoreConflicts)
			throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException {
		boolean hasWorkingTreeIterator = tw.getTreeCount() > T_FILE;
		return processEntry(treeWalk.getTree(T_BASE, CanonicalTreeParser.class),
				treeWalk.getTree(T_OURS, CanonicalTreeParser.class),
				treeWalk.getTree(T_THEIRS, CanonicalTreeParser.class),
				treeWalk.getTree(T_INDEX, DirCacheBuildIterator.class),
				hasWorkingTreeIterator ? treeWalk.getTree(T_FILE, WorkingTreeIterator.class) : null,
				ignoreConflicts);
	}

	/**
	 * Add files modified by model mergers to the index.
	 *
	 * @throws CorruptObjectException
	 * @throws MissingObjectException
	 * @throws IncorrectObjectTypeException
	 * @throws IOException
	 */
	private void indexModelMergedFiles()
			throws CorruptObjectException, MissingObjectException, IncorrectObjectTypeException, IOException {
		TreeWalk syncingTreeWalk = new TreeWalk(getRepository());
		try {
			syncingTreeWalk.addTree(new DirCacheIterator(dircache));
			syncingTreeWalk.addTree(new FileTreeIterator(getRepository()));
			syncingTreeWalk.setRecursive(true);
			syncingTreeWalk.setFilter(PathFilterGroup.createFromStrings(makeInSync));
			String lastAdded = null;
			while (syncingTreeWalk.next()) {
				String path = syncingTreeWalk.getPathString();
				if (path.equals(lastAdded)) {
					continue;
				}

				WorkingTreeIterator workingTree = syncingTreeWalk.getTree(1, WorkingTreeIterator.class);
				DirCacheIterator dirCache = syncingTreeWalk.getTree(0, DirCacheIterator.class);
				if (dirCache == null && workingTree != null && workingTree.isEntryIgnored()) {
					// nothing to do on this one
				} else if (workingTree != null) {
					if (dirCache == null || dirCache.getDirCacheEntry() == null
							|| !dirCache.getDirCacheEntry().isAssumeValid()) {
						final DirCacheEntry dce = new DirCacheEntry(path);
						final FileMode mode = workingTree.getIndexFileMode(dirCache);
						dce.setFileMode(mode);

						if (FileMode.GITLINK != mode) {
							dce.setLength(workingTree.getEntryLength());
							dce.setLastModified(workingTree.getEntryLastModified());
							InputStream is = workingTree.openEntryStream();
							try {
								dce.setObjectId(getObjectInserter().insert(Constants.OBJ_BLOB,
										workingTree.getEntryContentLength(), is));
							} finally {
								is.close();
							}
						} else {
							dce.setObjectId(workingTree.getEntryObjectId());
						}
						builder.add(dce);
						lastAdded = path;
					} else {
						builder.add(dirCache.getDirCacheEntry());
					}
				} else if (dirCache != null && FileMode.GITLINK == dirCache.getEntryFileMode()) {
					builder.add(dirCache.getDirCacheEntry());
				}
			}
		} finally {
			syncingTreeWalk.close();
		}
	}

	private static String getRepoRelativePath(IResource file) {
		final RepositoryMapping mapping = RepositoryMapping.getMapping(file);
		if (mapping != null) {
			return mapping.getRepoRelativePath(file);
		}
		return null;
	}

	/**
	 * On many aspects, team relies on the refreshed state of the workspace files, notably to determine if a
	 * file is in sync or not. Since we could have been called for a rebase, rebase that checked out a new
	 * commit without refreshing the workspace afterwards, team could see "in-sync" files even though they no
	 * longer exist in the workspace. This should be called before any merging takes place to make sure all
	 * files concerned by this merge operation are refreshed beforehand.
	 *
	 * @param resources
	 *            The set of resource roots to refresh.
	 * @throws CoreException
	 *             Thrown whenever we fail at refreshing one of the resources or its children.
	 */
	private void refreshRoots(IResource[] resources) throws CoreException {
		for (IResource root : resources) {
			if (root.isAccessible()) {
				root.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
			}
		}
	}

	private void markConflict(String filePath, DirCacheBuilder cacheBuilder,
			TreeParserResourceVariant baseVariant, TreeParserResourceVariant ourVariant,
			TreeParserResourceVariant theirVariant) {
		add(filePath, cacheBuilder, baseVariant, DirCacheEntry.STAGE_1);
		add(filePath, cacheBuilder, ourVariant, DirCacheEntry.STAGE_2);
		add(filePath, cacheBuilder, theirVariant, DirCacheEntry.STAGE_3);
	}

	private void add(String path, DirCacheBuilder cacheBuilder, TreeParserResourceVariant variant,
			int stage) {
		if (variant != null && !FileMode.TREE.equals(variant.getRawMode())) {
			DirCacheEntry e = new DirCacheEntry(path, stage);
			e.setFileMode(FileMode.fromBits(variant.getRawMode()));
			e.setObjectId(variant.getObjectId());
			e.setLastModified(0);
			e.setLength(0);
			cacheBuilder.add(e);
		}
	}

	private static class ModelMerge {
		private final RecursiveModelMerger merger;

		private final GitResourceVariantTreeSubscriber subscriber;

		private final RemoteResourceMappingContext remoteMappingContext;

		private final String path;

		private final Set<IResource> logicalModel;

		private final IResourceMappingMerger modelMerger;

		public ModelMerge(RecursiveModelMerger merger, GitResourceVariantTreeSubscriber subscriber,
				RemoteResourceMappingContext remoteMappingContext, String path, Set<IResource> logicalModel,
				IResourceMappingMerger modelMerger) {
			this.merger = merger;
			this.subscriber = subscriber;
			this.remoteMappingContext = remoteMappingContext;
			this.path = path;
			this.logicalModel = logicalModel;
			this.modelMerger = modelMerger;
		}

		private boolean run(IProgressMonitor monitor) throws CorruptObjectException, IOException {
			SubMonitor progress = SubMonitor.convert(monitor, 1);
			try {
				final IMergeContext mergeContext = prepareMergeContext();
				final IStatus status = modelMerger.merge(mergeContext, progress.newChild(1));
				registerHandledFiles(mergeContext, status);
			} catch (CoreException e) {
				Activator.logError(e.getMessage(), e);
				merger.cleanUp();
				return false;
			} catch (OperationCanceledException e) {
				final String message = NLS.bind(MergeText.RecursiveModelMerger_ScopeInitializationInterrupted,
						path);
				Activator.logError(message, e);
				merger.cleanUp();
				return false;
			}
			return true;
		}

		private void registerHandledFiles(final IMergeContext mergeContext, final IStatus status)
				throws TeamException, CoreException {
			for (IResource handledFile : logicalModel) {
				String filePath = getRepoRelativePath(handledFile);
				if (filePath == null) {
					IResourceVariant sourceVariant = subscriber.getSourceTree()
							.getResourceVariant(handledFile);
					IResourceVariant remoteVariant = subscriber.getRemoteTree()
							.getResourceVariant(handledFile);
					IResourceVariant baseVariant = subscriber.getBaseTree().getResourceVariant(handledFile);
					if (sourceVariant instanceof AbstractGitResourceVariant) {
						// In the case of a conflict, the file may not exist
						// So don't check for file existence
						filePath = ((AbstractGitResourceVariant)sourceVariant).getPath();
					}
					if (filePath == null && remoteVariant instanceof AbstractGitResourceVariant) {
						// In the case of a conflict, the file may not exist
						// So don't check for file existence
						filePath = ((AbstractGitResourceVariant)remoteVariant).getPath();
					}
					if (filePath == null && baseVariant instanceof AbstractGitResourceVariant) {
						// In the case of a conflict, the file may not exist
						// So don't check for file existence
						filePath = ((AbstractGitResourceVariant)baseVariant).getPath();
					}

				}
				if (LOGGER.isDebugEnabled()) {
					LOGGER.debug("Registering handled file " + filePath); //$NON-NLS-1$
				}
				if (filePath != null) {
					merger.modifiedFiles.add(filePath);
					merger.handledPaths.add(filePath);
				} else if (LOGGER.isDebugEnabled()) {
					LOGGER.debug("Impossible to compute a repo-relative filePath for file " //$NON-NLS-1$
							+ handledFile);
				}

				// The merge failed. If some parts of the model were
				// auto-mergeable, the model merger told us so through
				// GitMergeContext#markAsMerged() (stored within #makeInSync).
				// All other components of the logical model should be marked as
				// conflicts.
				if (mergeContext.getDiffTree().getDiff(handledFile) == null) {
					// If no diff, the model merger does... nothing
					// Make sure this file will be added to the index.
					merger.registerMergedPath(filePath);
					if (LOGGER.isDebugEnabled()) {
						LOGGER.debug("Merged non-modified file: " + filePath); //$NON-NLS-1$
					}
				} else if (filePath != null && status.getSeverity() != IStatus.OK
						&& !merger.makeInSync.contains(filePath)) {
					merger.unmergedPaths.add(filePath);
					merger.mergeResults.put(filePath,
							new MergeResult<RawText>(Collections.<RawText> emptyList()));
					final TreeParserResourceVariant baseVariant = (TreeParserResourceVariant)subscriber
							.getBaseTree().getResourceVariant(handledFile);
					final TreeParserResourceVariant ourVariant = (TreeParserResourceVariant)subscriber
							.getSourceTree().getResourceVariant(handledFile);
					final TreeParserResourceVariant theirVariant = (TreeParserResourceVariant)subscriber
							.getRemoteTree().getResourceVariant(handledFile);
					merger.markConflict(filePath, merger.builder, baseVariant, ourVariant, theirVariant);

					if (LOGGER.isDebugEnabled()) {
						LOGGER.debug("Marking conflict on " + filePath); //$NON-NLS-1$
					}
				}
			}
		}

		/**
		 * Create and initialize the merge context for the given model.
		 *
		 * @return An initialized merge context for the given model.
		 * @throws CoreException
		 *             Thrown if we cannot initialize the scope for this merge context.
		 * @throws OperationCanceledException
		 *             Thrown if the user cancelled the initialization.
		 */
		private IMergeContext prepareMergeContext() throws CoreException, OperationCanceledException {
			final Set<ResourceMapping> allMappings = LogicalModels.getResourceMappings(logicalModel,
					remoteMappingContext);
			final ResourceMapping[] mappings = allMappings.toArray(new ResourceMapping[allMappings.size()]);

			final ISynchronizationScopeManager manager = new SubscriberScopeManager(subscriber.getName(),
					mappings, subscriber, remoteMappingContext, true) {
				@Override
				public ISchedulingRule getSchedulingRule() {
					return RuleUtil.getRule(merger.getRepository());
				}
			};
			manager.initialize(new NullProgressMonitor());

			final IMergeContext context = new GitMergeContext(merger, subscriber, manager);
			// Wait for the asynchronous scope expanding to end (started from
			// the initialization of our merge context)
			waitForScope(context);

			return context;
		}

		private void waitForScope(IMergeContext context) {
			// The UILockListener might prevent us from properly joining.
			boolean joined = false;
			while (!joined) {
				try {
					Job.getJobManager().join(context, new NullProgressMonitor());
					joined = true;
				} catch (InterruptedException e) {
					// Some other UI threads were trying to run. Let the
					// syncExecs do their jobs and re-try to join on ours.
				}
			}
		}
	}

	private static class GitMergeContext extends SubscriberMergeContext {

		private final RecursiveModelMerger merger;

		/**
		 * Create and initialize a merge context for the given subscriber.
		 *
		 * @param merger
		 *            the merger
		 * @param subscriber
		 *            the subscriber.
		 * @param scopeManager
		 *            the scope manager.
		 */
		public GitMergeContext(RecursiveModelMerger merger, Subscriber subscriber,
				ISynchronizationScopeManager scopeManager) {
			super(subscriber, scopeManager);
			this.merger = merger;
			initialize();
		}

		public void markAsMerged(IDiff node, boolean inSyncHint, IProgressMonitor monitor)
				throws CoreException {
			final IResource resource = getDiffTree().getResource(node);
			merger.addSyncPath(resource);
		}

		public void reject(IDiff diff, IProgressMonitor monitor) throws CoreException {
			// Empty implementation
		}

		@Override
		protected void makeInSync(IDiff diff, IProgressMonitor monitor) throws CoreException {
			final IResource resource = getDiffTree().getResource(diff);
			merger.addSyncPath(resource);
		}
	}

	private void addSyncPath(IResource resource) {
		String repoRelativePath = getRepoRelativePath(resource);
		registerMergedPath(repoRelativePath);
	}

	private boolean registerMergedPath(String path) {
		if (path != null) {
			return makeInSync.add(path);
		}
		return false;
	}
}
// CHECKSTYLE:ON

Back to the top