/*******************************************************************************
* Copyright (C) 2010, 2015 Dariusz Luksza <dariusz@luksza.org> 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
*******************************************************************************/
package org.eclipse.egit.core.synchronize;
import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.eclipse.jgit.lib.Constants.MASTER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
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.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.NullProgressMonitor;
import org.eclipse.egit.core.op.ConnectProviderOperation;
import org.eclipse.egit.core.project.RepositoryMapping;
import org.eclipse.egit.core.synchronize.dto.GitSynchronizeData;
import org.eclipse.egit.core.synchronize.dto.GitSynchronizeDataSet;
import org.eclipse.egit.core.test.GitTestCase;
import org.eclipse.egit.core.test.TestProject;
import org.eclipse.egit.core.test.TestRepository;
import org.eclipse.egit.core.test.models.SampleModelProvider;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.team.core.variants.IResourceVariant;
import org.eclipse.team.core.variants.IResourceVariantTree;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class GitResourceVariantTreeTest extends GitTestCase {
private Repository repo;
private IProject iProject;
private TestRepository testRepo;
@Before
public void createGitRepository() throws Exception {
iProject = project.project;
testRepo = new TestRepository(gitDir);
testRepo.connect(iProject);
repo = RepositoryMapping.getMapping(iProject).getRepository();
}
@After
public void clearGitResources() throws Exception {
testRepo.disconnect(iProject);
testRepo.dispose();
repo = null;
super.tearDown();
}
/**
* roots() method should return list of projects that are associated with
* given repository. In this case there is only one project associated with
* this repository therefore only one root should be returned.
*
* @throws Exception
*/
@Test
public void shouldReturnOneRoot() throws Exception {
// when
try (Git git = new Git(repo)) {
git.commit().setAuthor("JUnit", "junit@egit.org")
.setMessage("Initial commit").call();
}
GitSynchronizeData data = new GitSynchronizeData(repo, HEAD, HEAD,
false);
GitSynchronizeDataSet dataSet = new GitSynchronizeDataSet(data);
// given
GitResourceVariantTree grvt = new GitTestResourceVariantTree(dataSet,
null, null);
// then
assertEquals(1, grvt.roots().length);
IResource actualProject = grvt.roots()[0];
assertEquals(this.project.getProject(), actualProject);
}
/**
* When we have two or more project associated with repository, roots()
* method should return list of project. In this case we have two project
* associated with particular repository, therefore '2' value is expected.
*
* @throws Exception
*/
@Test
public void shouldReturnTwoRoots() throws Exception {
// when
// create second project
TestProject secondProject = new TestProject(true, "Project-2");
try {
IProject secondIProject = secondProject.project;
// add connect project with repository
new ConnectProviderOperation(secondIProject, gitDir).execute(null);
try (Git git = new Git(repo)) {
git.commit().setAuthor("JUnit", "junit@egit.org")
.setMessage("Initial commit").call();
}
GitSynchronizeData data = new GitSynchronizeData(repo, HEAD, HEAD,
false);
GitSynchronizeDataSet dataSet = new GitSynchronizeDataSet(data);
// given
GitResourceVariantTree grvt = new GitTestResourceVariantTree(dataSet,
null, null);
// then
IResource[] roots = grvt.roots();
// sort in order to be able to assert the project instances
Arrays.sort(roots, new Comparator<IResource>() {
@Override
public int compare(IResource r1, IResource r2) {
String path1 = r1.getFullPath().toString();
String path2 = r2.getFullPath().toString();
return path1.compareTo(path2);
}
});
assertEquals(2, roots.length);
IResource actualProject = roots[0];
assertEquals(this.project.project, actualProject);
IResource actualProject1 = roots[1];
assertEquals(secondIProject, actualProject1);
} finally {
secondProject.dispose();
}
}
/**
* Checks that getResourceVariant will not throw NPE for null argument. This
* method is called with null argument when local or remote resource does
* not exist.
*
* @throws Exception
*/
@Test
public void shouldReturnNullResourceVariant() throws Exception {
// when
try (Git git = new Git(repo)) {
git.commit().setAuthor("JUnit", "junit@egit.org")
.setMessage("Initial commit").call();
}
GitSynchronizeData data = new GitSynchronizeData(repo, HEAD, MASTER,
false);
GitSynchronizeDataSet dataSet = new GitSynchronizeDataSet(data);
// given
GitResourceVariantTree grvt = new GitRemoteResourceVariantTree(null,
dataSet);
// then
assertNull(grvt.getResourceVariant(null));
}
/**
* getResourceVariant() should return null when given resource doesn't exist
* in repository.
*
* @throws Exception
*/
@Test
public void shouldReturnNullResourceVariant2() throws Exception {
// when
IPackageFragment iPackage = project.createPackage("org.egit.test");
IType mainJava = project.createType(iPackage, "Main.java",
"class Main {}");
try (Git git = new Git(repo)) {
git.commit().setAuthor("JUnit", "junit@egit.org")
.setMessage("Initial commit").call();
}
GitSynchronizeData data = new GitSynchronizeData(repo, HEAD, MASTER,
false);
GitSynchronizeDataSet dataSet = new GitSynchronizeDataSet(data);
GitSyncCache cache = GitSyncCache.getAllData(dataSet,
new NullProgressMonitor());
// given
GitResourceVariantTree grvt = new GitRemoteResourceVariantTree(cache,
dataSet);
// then
assertNull(grvt.getResourceVariant(mainJava.getResource()));
}
/**
* Check if getResourceVariant() does return the same resource that was
* committed. Passes only when it is run as a single test, not as a part of
* largest test suite
*
* @throws Exception
*/
@Test
public void shoulReturnSameResourceVariant() throws Exception {
// when
String fileName = "Main.java";
File file = testRepo.createFile(iProject, fileName);
testRepo.appendContentAndCommit(iProject, file, "class Main {}",
"initial commit");
IFile mainJava = testRepo.getIFile(iProject, file);
GitSynchronizeData data = new GitSynchronizeData(repo, HEAD, MASTER,
false);
GitSynchronizeDataSet dataSet = new GitSynchronizeDataSet(data);
GitSyncCache cache = GitSyncCache.getAllData(dataSet,
new NullProgressMonitor());
// given
GitResourceVariantTree grvt = new GitRemoteResourceVariantTree(cache,
dataSet);
// then
// null variant indicates that resource wasn't changed
assertNull(grvt.getResourceVariant(mainJava));
}
@Test
public void shouldNotReturnNullOnSameResouceVariant() throws Exception {
String modifiedFileName = "changingFile."
+ SampleModelProvider.SAMPLE_FILE_EXTENSION;
String unchangedFileName = "notChangingFile."
+ SampleModelProvider.SAMPLE_FILE_EXTENSION;
String removedFileName = "toBeRemovedFile."
+ SampleModelProvider.SAMPLE_FILE_EXTENSION;
File modifiedFile = testRepo.createFile(iProject, modifiedFileName);
File unchangedFile = testRepo.createFile(iProject, unchangedFileName);
File removedFile = testRepo.createFile(iProject, removedFileName);
testRepo.appendFileContent(modifiedFile, "My content is changing");
testRepo.appendFileContent(unchangedFile, "My content is constant");
testRepo.appendFileContent(removedFile, "I will be removed");
IFile iModifiedFile = testRepo.getIFile(iProject, modifiedFile);
IFile iUnchangedFile = testRepo.getIFile(iProject, unchangedFile);
IFile iRemovedFile = testRepo.getIFile(iProject, removedFile);
testRepo.trackAllFiles(iProject);
RevCommit firstCommit = testRepo.commit("C1");
testRepo.appendFileContent(modifiedFile, " My content has changed");
testRepo.track(modifiedFile);
testRepo.removeFromIndex(removedFile);
RevCommit secondCommit = testRepo.commit("C2");
//@formatter:off
// History (X means has changed)
//------------------------------------------------------------
// files C1 [HEAD] C2
// changingFile.sample |-----X----------|-------X-------|->
// notChangingFile.sample |-----X----------|---------------|->
// toBeRemovedFile.sample |-----X----------|-------X-------|->
//-------------------------------------------------------------
//@formatter:on
testRepo.checkoutBranch(firstCommit.getName());
iProject.refreshLocal(IResource.DEPTH_INFINITE,
new NullProgressMonitor());
// Now synchronize the two commits using our logical model provider
SampleModelProvider provider = new SampleModelProvider();
// Get the affected resources
ResourceMapping[] mappings = provider
.getMappings(iModifiedFile,
ResourceMappingContext.LOCAL_CONTEXT,
new NullProgressMonitor());
Set<IResource> includedResource = collectResources(mappings);
Set<IResource> expectedIncludedResources = new HashSet<IResource>();
expectedIncludedResources.add(iModifiedFile);
expectedIncludedResources.add(iUnchangedFile);
expectedIncludedResources.add(iRemovedFile);
assertEquals(expectedIncludedResources, includedResource);
// Synchronize the data
final GitSynchronizeData data = new GitSynchronizeData(
testRepo.getRepository(), firstCommit.getName(),
secondCommit.getName(), true, includedResource);
GitSynchronizeDataSet gitSynchDataSet = new GitSynchronizeDataSet(data);
final GitResourceVariantTreeSubscriber subscriber = new GitResourceVariantTreeSubscriber(
gitSynchDataSet);
subscriber.init(new NullProgressMonitor());
IResourceVariantTree sourceVariantTree = subscriber.getSourceTree();
assertNotNull(sourceVariantTree);
IResourceVariantTree remoteVariantTree = subscriber.getRemoteTree();
assertNotNull(remoteVariantTree);
// In the use case in which the file has been deleted the source variant is
// not null whereas the remote variant is null.It seems quite logic.
// However in the second use case we have the same result, the source variant is
// not null whereas the remote is null. In both cases the null value does
// not mean the same thing. In the first case, the null value means that
// the resource is no longer in the repository and in the second the
// null value means there is no change between the two versions.
// Using these values I am not able to distinguish both case.
// It is in contradiction with test #shouldReturnNullResourceVariant2()
// and test #shoulReturnSameResourceVariant(). However I haven't found
// another way to handle this case. Maybe something can be
// done with ThreeWayDiffEntry.scan(tw) to force including in the cache
// some entry even if they have not changed. For example,
// ThreeWayDiffEntry.scan(tw,includedSource) or maybe try preventing the variant
// tree to return null by walking throught the repository and looking for the file...
IResourceVariant unchangedSourceVariant = sourceVariantTree
.getResourceVariant(iUnchangedFile);
IResourceVariant unchangedRemoteVariant = remoteVariantTree
.getResourceVariant(iUnchangedFile);
assertNotNull(unchangedSourceVariant);
assertNotNull(unchangedRemoteVariant);
IResourceVariant removedSourceVariant = sourceVariantTree
.getResourceVariant(iRemovedFile);
IResourceVariant removedRemoteVariant = remoteVariantTree
.getResourceVariant(iRemovedFile);
assertNotNull(removedSourceVariant);
assertNull(removedRemoteVariant);
GitSubscriberResourceMappingContext context = new GitSubscriberResourceMappingContext(subscriber, gitSynchDataSet);
assertFalse(context.hasLocalChange(iUnchangedFile,
new NullProgressMonitor()));
assertFalse(context.hasRemoteChange(iUnchangedFile,
new NullProgressMonitor()));
assertFalse(context.hasLocalChange(iModifiedFile,
new NullProgressMonitor()));
assertTrue(context.hasRemoteChange(iModifiedFile,
new NullProgressMonitor()));
assertFalse(context.hasLocalChange(iRemovedFile,
new NullProgressMonitor()));
assertTrue(context.hasRemoteChange(iRemovedFile,
new NullProgressMonitor()));
}
private static Set<IResource> collectResources(ResourceMapping[] mappings)
throws CoreException {
final Set<IResource> resources = new HashSet<IResource>();
ResourceMappingContext context = ResourceMappingContext.LOCAL_CONTEXT;
for (ResourceMapping mapping : mappings) {
ResourceTraversal[] traversals = mapping.getTraversals(context,
new NullProgressMonitor());
for (ResourceTraversal traversal : traversals) {
resources.addAll(Arrays.asList(traversal.getResources()));
}
}
return resources;
}
/**
* Create and commit Main.java file in master branch, then create branch
* "test" checkout nearly created branch and modify Main.java file.
* getResourceVariant() should obtain Main.java file content from "master"
* branch. Passes only when it is run as a single test, not as a part of
* largest test suite
*
* @throws Exception
*/
@Test
public void shouldReturnDifferentResourceVariant() throws Exception {
// when
String fileName = "Main.java";
File file = testRepo.createFile(iProject, fileName);
testRepo.appendContentAndCommit(iProject, file, "class Main {}",
"initial commit");
IFile mainJava = testRepo.getIFile(iProject, file);
testRepo.createAndCheckoutBranch(Constants.R_HEADS + Constants.MASTER,
Constants.R_HEADS + "test");
testRepo.appendContentAndCommit(iProject, file, "// test",
"first commit");
GitSynchronizeData data = new GitSynchronizeData(repo, HEAD, MASTER,
true);
GitSynchronizeDataSet dataSet = new GitSynchronizeDataSet(data);
GitSyncCache cache = GitSyncCache.getAllData(dataSet,
new NullProgressMonitor());
// given
GitResourceVariantTree grvt = new GitBaseResourceVariantTree(cache,
dataSet);
// then
IResourceVariant actual = grvt.getResourceVariant(mainJava);
assertNotNull(actual);
assertEquals(fileName, actual.getName());
InputStream actualIn = actual.getStorage(new NullProgressMonitor())
.getContents();
byte[] actualByte = getBytesAndCloseStream(actualIn);
InputStream expectedIn = mainJava.getContents();
byte[] expectedByte = getBytesAndCloseStream(expectedIn);
// assert arrays not equals
assertFalse(Arrays.equals(expectedByte, actualByte));
}
private byte[] getBytesAndCloseStream(InputStream stream) throws Exception {
try {
byte[] actualByte = new byte[stream.available()];
stream.read(actualByte);
return actualByte;
} finally {
stream.close();
}
}
}