blob: 2a9d1059228ae656b919c6d603279e31ea3dabfd [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016, 2017 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
* Martin Fleck - bug 512562
*******************************************************************************/
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.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 {
/**
* Used to specify that there is only one project in the repository. The tester does not have to specify
* it.
*/
public static final String COMPARE_NO_PROJECT_SELECTED = "noProject"; //$NON-NLS-1$
/** The result of the merge operation. */
private MergeResult mergeResult;
/** The result of the rebase operation. */
private RebaseResult rebaseResult;
/** The result of the cherry-pick operation. */
private CherryPickResult cherryPickResult;
public Repository getRepository() {
return repository;
}
/**
* Get the list of projects in the workspace. All projects are refreshed before being returned.
*
* @return the list of all projects
* @throws CoreException
* Thrown if the refresh operation went wrong
*/
public List<IProject> getProjects() throws CoreException {
for (IProject project : projects) {
project.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
}
return Lists.newArrayList(projects);
}
/**
* Get The JGit Status Object from the repository.
*
* @return the status Object of the Git repository
* @throws Exception
* If the status cannot be retrieved
*/
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
* Thrown if the merge operation or the refresh of projects went wrong
* @throws IOException
* Thrown if the checkout operation went wrong
* @throws InterruptedException
* Thrown if the checkout operation went wrong
*/
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
* Thrown if the cherry-pick operation or the refresh of projects went wrong
* @throws IOException
* Thrown if the checkout operation went wrong
* @throws InterruptedException
* Thrown if the checkout operation went wrong
*/
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
* Thrown if the rebase operation or the refresh of projects went wrong
* @throws IOException
* Thrown if the checkout operation went wrong
* @throws InterruptedException
* Thrown if the checkout operation went wrong
*/
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
* Thrown if the checkout operation went wrong
* @throws CoreException
* Thrown if the checkout operation went wrong
*/
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
* Thrown if the checkout operation went wrong
* @throws CoreException
* Thrown if the checkout operation went wrong
*/
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();
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,
EMFCompareIDEUIPlugin.getDefault().getModelMinimizerRegistry().getCompoundMinimizer(),
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());
}
/**
* Get the resources associated with the given mappings.
*
* @param mappings
* An array of resourceMappings
* @param context
* The context of resourceMappings
* @return a list of resources
*/
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
* Thrown if the merge operation or the refresh of projects went wrong
* @throws IOException
* Thrown if the cannot retrieve the current branch
*/
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(repository);
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();
}
}