Skip to main content
summaryrefslogtreecommitdiffstats
blob: 2fd84e47f9d2251f6bd014272882718ffbdac582 (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
/*******************************************************************************
 * Copyright (c) 2016 Obeo and others.
 * 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
 *     Philip Langer - add convenience methods
 *******************************************************************************/
package org.eclipse.emf.compare.ide.ui.tests.git.framework;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;

import com.google.common.collect.Lists;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.compare.ITypedElement;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.mapping.RemoteResourceMappingContext;
import org.eclipse.core.resources.mapping.ResourceMapping;
import org.eclipse.core.resources.mapping.ResourceMappingContext;
import org.eclipse.core.resources.mapping.ResourceTraversal;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.egit.core.Activator;
import org.eclipse.egit.core.internal.util.ResourceUtil;
import org.eclipse.egit.core.op.BranchOperation;
import org.eclipse.egit.core.op.CherryPickOperation;
import org.eclipse.egit.core.op.MergeOperation;
import org.eclipse.egit.core.op.RebaseOperation;
import org.eclipse.egit.core.op.ResetOperation;
import org.eclipse.egit.core.synchronize.GitResourceVariantTreeSubscriber;
import org.eclipse.egit.core.synchronize.GitSubscriberResourceMappingContext;
import org.eclipse.egit.core.synchronize.dto.GitSynchronizeData;
import org.eclipse.egit.core.synchronize.dto.GitSynchronizeDataSet;
import org.eclipse.emf.common.util.BasicMonitor;
import org.eclipse.emf.compare.Comparison;
import org.eclipse.emf.compare.EMFCompare;
import org.eclipse.emf.compare.EMFCompare.Builder;
import org.eclipse.emf.compare.ide.ui.internal.EMFCompareIDEUIPlugin;
import org.eclipse.emf.compare.ide.ui.internal.logical.ComparisonScopeBuilder;
import org.eclipse.emf.compare.ide.ui.internal.logical.IdenticalResourceMinimizer;
import org.eclipse.emf.compare.ide.ui.internal.logical.StorageTypedElement;
import org.eclipse.emf.compare.ide.ui.internal.logical.SubscriberStorageAccessor;
import org.eclipse.emf.compare.ide.ui.logical.IModelResolver;
import org.eclipse.emf.compare.ide.ui.logical.IStorageProvider;
import org.eclipse.emf.compare.ide.ui.logical.IStorageProviderAccessor;
import org.eclipse.emf.compare.ide.ui.tests.git.framework.internal.statements.InternalGitTestSupport;
import org.eclipse.emf.compare.rcp.internal.extension.impl.EMFCompareBuilderConfigurator;
import org.eclipse.emf.compare.scope.IComparisonScope;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.jgit.api.CheckoutResult;
import org.eclipse.jgit.api.CherryPickResult;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.MergeResult;
import org.eclipse.jgit.api.RebaseResult;
import org.eclipse.jgit.api.ResetCommand.ResetType;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.team.core.subscribers.SubscriberScopeManager;

/**
 * This class contains methods to perform git operations in the context of an EMFCompare test. This class may
 * be injected in client.
 * 
 * @author <a href="mailto:mathieu.cartaud@obeo.fr">Mathieu Cartaud</a>
 */
@SuppressWarnings({"restriction" })
public class GitTestSupport extends InternalGitTestSupport {

	public final static String COMPARE_NO_PROJECT_SELECTED = "noProject"; //$NON-NLS-1$

	private MergeResult mergeResult;

	private RebaseResult rebaseResult;

	private CherryPickResult cherryPickResult;

	public Repository getRepository() {
		return repository;
	}

	public List<IProject> getProjects() throws CoreException {
		for (IProject project : projects) {
			project.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
		}
		return Lists.newArrayList(projects);
	}

	public Status getStatus() throws Exception {
		Git git = new Git(repository);
		try {
			return git.status().call();
		} finally {
			git.close();
		}
	}

	public MergeResult getMergeResult() {
		return mergeResult;
	}

	public RebaseResult getRebaseResult() {
		return rebaseResult;
	}

	public CherryPickResult getCherryPickResult() {
		return cherryPickResult;
	}

	/**
	 * Merge two branches with the given merge strategy.
	 * 
	 * @param local
	 *            The checkouted branch (for example "master" or "refs/for/master", both syntaxes are
	 *            accepted)
	 * @param remote
	 *            The branch to merge with (for example "master" or "refs/for/master", both syntaxes are
	 *            accepted)
	 * @throws CoreException
	 * @throws IOException
	 * @throws InterruptedException
	 */
	public void merge(String local, String remote) throws CoreException, IOException, InterruptedException {
		checkoutBranch(normalizeBranch(local));
		MergeOperation op = new MergeOperation(repository, normalizeBranch(remote));
		op.execute(new NullProgressMonitor());
		mergeResult = op.getResult();
		for (IProject iProject : projects) {
			iProject.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
		}
	}

	/**
	 * Cherry-pick the commit located on the given "from" branch to the given "to" branch.
	 * 
	 * @param local
	 *            The branch on with the commit will be cherry-picked (for example "master" or
	 *            "refs/for/master", both syntaxes are accepted)
	 * @param remote
	 *            The branch where the commit will be cherry-picked (for example "master" or
	 *            "refs/for/master", both syntaxes are accepted)
	 * @throws CoreException
	 * @throws IOException
	 * @throws InterruptedException
	 */
	public void cherryPick(String local, String remote)
			throws CoreException, IOException, InterruptedException {
		checkoutBranch(normalizeBranch(local));
		RevWalk revWalk = new RevWalk(repository);
		try {
			RevCommit commitId = revWalk.parseCommit(repository.findRef(remote).getObjectId());
			CherryPickOperation op = new CherryPickOperation(repository, commitId);
			op.execute(new NullProgressMonitor());
			cherryPickResult = op.getResult();
			for (IProject iProject : projects) {
				iProject.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
			}
		} finally {
			revWalk.close();
		}
	}

	/**
	 * Rebase the given from branch on the to branch.
	 * 
	 * @param local
	 *            The checkouted branch (for example "master" or "refs/for/master", both syntaxes are
	 *            accepted)
	 * @param remote
	 *            The branch to rebase on (for example "master" or "refs/for/master", both syntaxes are
	 *            accepted)
	 * @throws CoreException
	 * @throws IOException
	 * @throws InterruptedException
	 */
	public void rebase(String local, String remote) throws CoreException, IOException, InterruptedException {
		checkoutBranch(normalizeBranch(local));
		RebaseOperation op = new RebaseOperation(repository, repository.findRef(remote));
		op.execute(new NullProgressMonitor());
		rebaseResult = op.getResult();
		for (IProject iProject : projects) {
			iProject.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
		}
	}

	/**
	 * <pre>
	 * Compare the file on the given path between the two given branches. This method is attended to be used
	 * when their is only one project in the repository.
	 * 
	 * If their is multiple projects use {@code compare(from, to , fileName, containerName)} with the name of 
	 * the containing project instead.
	 * </pre>
	 * 
	 * @param from
	 *            The branch checkouted (for example "master" or "refs/for/master", both syntaxes are
	 *            accepted)
	 * @param to
	 *            The branch to compare with (for example "master" or "refs/for/master", both syntaxes are
	 *            accepted)
	 * @param fileName
	 *            The file to compare (the relative path to the file from the project)
	 * @return the comparison
	 * @throws IOException
	 * @throws CoreException
	 */
	public Comparison compare(String from, String to, String fileName) throws IOException, CoreException {
		return compare(from, to, fileName, COMPARE_NO_PROJECT_SELECTED);
	}

	/**
	 * <pre>
	 * Compare the file on the given path between the two given branches. This method is attended to be used
	 * when their is several projects in the repository.
	 *  
	 * If their is only one project use {@code compare(from, to , fileName)} instead or give the value
	 * {@code EMFCompareGitTestsSupport.COMPARE_NO_PROJECT_SELECTED} to the parameter containerProject.
	 * </pre>
	 * 
	 * @param from
	 *            The branch checkouted (for example "master" or "refs/for/master", both syntaxes are
	 *            accepted)
	 * @param to
	 *            The branch to compare with (for example "master" or "refs/for/master", both syntaxes are
	 *            accepted)
	 * @param fileName
	 *            The file to compare (the relative path to the file from the project)
	 * @param containerProject
	 *            The project containing the file to compare. If
	 *            {@code EMFCompareGitTestsSupport.COMPARE_NO_PROJECT_SELECTED} is used, the first correct
	 *            file will be used (use this when their is only one project)
	 * @return the comparison
	 * @throws IOException
	 * @throws CoreException
	 */
	public Comparison compare(String from, String to, String fileName, String containerProject)
			throws IOException, CoreException {
		String normalizedFrom = normalizeBranch(from);
		String normalizedTo = normalizeBranch(to);
		IFile file = null;
		for (IProject project : projects) {
			if (!containerProject.equals(COMPARE_NO_PROJECT_SELECTED)) {
				if (project.getName().equals(containerProject)) {
					file = project.getFile(fileName);
					break;
				}
			} else {
				file = project.getFile(fileName);
				break;
			}
		}

		if (file == null || !file.exists()) {
			throw new IllegalArgumentException("Could not find file " + fileName + ": wrong test set-up?"); //$NON-NLS-1$ //$NON-NLS-2$
		}

		checkoutBranch(normalizedFrom);
		final String fullPath = file.getFullPath().toString();
		final GitSynchronizeData data = new GitSynchronizeData(getRepository(), normalizedFrom, normalizedTo,
				true);
		final GitSynchronizeDataSet gsds = new GitSynchronizeDataSet(data);
		final GitResourceVariantTreeSubscriber subscriber = new GitResourceVariantTreeSubscriber(gsds);
		subscriber.init(new NullProgressMonitor());
		final GitSubscriberResourceMappingContext context = new GitSubscriberResourceMappingContext(
				subscriber, gsds);

		final Set<IResource> includedResources = new HashSet<IResource>(Arrays.asList(file));
		final Set<ResourceMapping> allMappings = new HashSet<ResourceMapping>();

		Set<IResource> newResources = new HashSet<IResource>(includedResources);
		do {
			final Set<IResource> copy = newResources;
			newResources = new HashSet<IResource>();
			for (IResource resource : copy) {
				ResourceMapping[] mappings = ResourceUtil.getResourceMappings(resource, context);
				allMappings.addAll(Arrays.asList(mappings));
				newResources.addAll(collectResources(mappings, context));
			}
		} while (includedResources.addAll(newResources));

		// Launch the comparison now that the logical model is computed
		// and can be provided to a new GitSynchronizeData object
		final ResourceMapping[] mappings = allMappings.toArray(new ResourceMapping[allMappings.size()]);
		final GitSynchronizeData gsdThatCoverLogicalModel = new GitSynchronizeData(repository, normalizedFrom,
				normalizedTo, true, includedResources);
		final GitSynchronizeDataSet gsds2 = new GitSynchronizeDataSet(gsdThatCoverLogicalModel);
		final GitResourceVariantTreeSubscriber subscriber2 = new GitResourceVariantTreeSubscriber(gsds2);
		RemoteResourceMappingContext remoteContext = new GitSubscriberResourceMappingContext(subscriber2,
				gsds2);
		final SubscriberScopeManager subscriberScopeManager = new SubscriberScopeManager(
				subscriber2.getName(), mappings, subscriber2, remoteContext, true);
		subscriber2.init(new NullProgressMonitor());
		disposers.add(new Runnable() {
			public void run() {
				subscriber.dispose();
				gsds.dispose();
				subscriber2.dispose();
				gsds2.dispose();
				subscriberScopeManager.dispose();
			}
		});

		final IStorageProviderAccessor accessor = new SubscriberStorageAccessor(subscriber2);
		final IStorageProvider remoteProvider = accessor.getStorageProvider(file,
				IStorageProviderAccessor.DiffSide.REMOTE);
		final IStorageProvider ancestorProvider = accessor.getStorageProvider(file,
				IStorageProviderAccessor.DiffSide.ORIGIN);
		assertNotNull(remoteProvider);
		assertNotNull(ancestorProvider);

		final IProgressMonitor monitor = new NullProgressMonitor();
		// do we really need to create a new one?
		final ITypedElement left = new StorageTypedElement(file, fullPath);
		final ITypedElement right = new StorageTypedElement(remoteProvider.getStorage(monitor), fullPath);
		final ITypedElement origin = new StorageTypedElement(ancestorProvider.getStorage(monitor), fullPath);

		EMFCompareIDEUIPlugin p = EMFCompareIDEUIPlugin.getDefault();
		IModelResolver resolver = p.getModelResolverRegistry().getBestResolverFor(file);

		final ComparisonScopeBuilder scopeBuilder = new ComparisonScopeBuilder(resolver,
				new IdenticalResourceMinimizer(), accessor);
		final IComparisonScope scope = scopeBuilder.build(left, right, origin, monitor);

		final ResourceSet leftResourceSet = (ResourceSet)scope.getLeft();
		final ResourceSet rightResourceSet = (ResourceSet)scope.getRight();
		final ResourceSet originResourceSet = (ResourceSet)scope.getOrigin();

		assertFalse(leftResourceSet.getResources().isEmpty());
		assertFalse(rightResourceSet.getResources().isEmpty());
		assertFalse(originResourceSet.getResources().isEmpty());

		final Builder comparisonBuilder = EMFCompare.builder();
		EMFCompareBuilderConfigurator.createDefault().configure(comparisonBuilder);
		return comparisonBuilder.build().compare(scope, new BasicMonitor());
	}

	private Set<IResource> collectResources(ResourceMapping[] mappings, ResourceMappingContext context) {
		final Set<IResource> resources = new HashSet<IResource>();
		for (ResourceMapping mapping : mappings) {
			try {
				ResourceTraversal[] traversals = mapping.getTraversals(context, new NullProgressMonitor());
				for (ResourceTraversal traversal : traversals) {
					resources.addAll(Arrays.asList(traversal.getResources()));
				}
			} catch (CoreException e) {
				Activator.logError(e.getMessage(), e);
			}
		}
		return resources;
	}

	/**
	 * Checkout repository to the given branch.
	 * 
	 * @param refName
	 *            The branch to checkout (for example "master" or "refs/heads/master", both syntaxes are
	 *            accepted)
	 * @throws CoreException
	 */
	public void checkoutBranch(String refName) throws CoreException, IOException {
		ResetOperation reset = new ResetOperation(repository, repository.getBranch(), ResetType.HARD);
		reset.execute(null);
		BranchOperation op = new BranchOperation(getRepository(), normalizeBranch(refName));
		op.execute(null);
		CheckoutResult result = op.getResult();
		if (result.getStatus() != CheckoutResult.Status.OK) {
			throw new IllegalStateException("Unable to checkout branch " + refName + " result:" + result); //$NON-NLS-1$ //$NON-NLS-2$
		}
	}

	/**
	 * Specifies whether the repository is in conflict state or not.
	 * <p>
	 * This is a convenience method for <code>getStatus().getConflicting().isEmpty()</code>.
	 * </p>
	 * 
	 * @return <code>true</code> if the repository is in a conflict state, <code>false</code> otherwise.
	 * @throws Exception
	 *             if the status of the repository could not be created queried.
	 */
	public boolean noConflict() throws Exception {
		return getStatus().getConflicting().isEmpty();
	}

	/**
	 * Specifies whether the file given in <code>path</code> currently exists in the working tree of the
	 * repository.
	 * 
	 * @param path
	 *            The path to the file in question, such as <code>dir1/dir2/file1.txt</code>.
	 * @return <code>true</code> if the file in the given <code>path</code> exists, <code>false</code>
	 *         otherwise.
	 */
	public boolean fileExists(String path) {
		return new File(repository.getWorkTree(), path).exists();
	}
}

Back to the top